Transformer并行化训练笔记

最近在看transformer,以及相关的分布式实现,发现有些问题不太明白,顺便记录下,如有错误欢迎大佬指正。
在attention的介绍中(http://nlp.seas.harvard.edu/2018/04/03/attention.html),对并行化的训练给出了代码,但没有做详细的介绍,这里仔细的分析下相关代码:

# Skip if not interested in multigpu.
class MultiGPULossCompute:
    "A multi-gpu loss compute and train function."
    def __init__(self, generator, criterion, devices, opt=None, chunk_size=5):
        # Send out to different gpus.
        self.generator = generator
        self.criterion = nn.parallel.replicate(criterion, 
                                               devices=devices)
        self.opt = opt
        self.devices = devices
        self.chunk_size = chunk_size
        
    def __call__(self, out, targets, normalize):
        total = 0.0
        # 复制linear激活函数
        generator = nn.parallel.replicate(self.generator,
                                          devices=self.devices)
        # 按batch切分decoder输出
        out_scatter = nn.parallel.scatter(out,
                                          target_gpus=self.devices)
        out_grad = [[] for _ in out_scatter]
        # 切分label
        targets = nn.parallel.scatter(targets,
                                      target_gpus=self.devices)

        # Divide generating into chunks.
        chunk_size = self.chunk_size
        # 切分句子
        for i in range(0, out_scatter[0].size(1), chunk_size):
            # Predict distributions
            # 将decoder输出按句子切分
            out_column = [[Variable(o[:, i:i + chunk_size].data,
                                    requires_grad=self.opt is not None)]
                          for o in out_scatter]
            # 执行linear函数
            gen = nn.parallel.parallel_apply(generator, out_column)

            # Compute loss. 
            y = [(g.contiguous().view(-1, g.size(-1)),
                  t[:, i:i + chunk_size].contiguous().view(-1))
                 for g, t in zip(gen, targets)]
            loss = nn.parallel.parallel_apply(self.criterion, y)

            # Sum and normalize loss
            l = nn.parallel.gather(loss,
                                   target_device=self.devices[0])
            l = l.sum() / normalize
            # print(l)
            total += l

            # Backprop loss to output of transformer
            if self.opt is not None:
                l.backward()
                for j, l in enumerate(loss):
                    out_grad[j].append(out_column[j][0].grad.data.clone())

        # Backprop all loss through transformer.            
        if self.opt is not None:
            out_grad = [Variable(torch.cat(og, dim=1)) for og in out_grad]
            o1 = out
            o2 = nn.parallel.gather(out_grad,
                                    target_device=self.devices[0])
            o1.backward(gradient=o2)
            self.opt.step()
            self.opt.optimizer.zero_grad()
        return total * normalize

其中,有一些torch的并行化原语:
scatter - split batches onto different gpus
parallel_apply - apply module to batches on different gpus
gather - pull scattered data back onto one gpu.
nn.DataParallel - a special module wrapper that calls these all before evaluating.

官方的解释:
replicate: replicate a Module on multiple devices
scatter: distribute the input in the first-dimension
gather: gather and concatenate the input in the first-dimension
parallel_apply: apply a set of already-distributed inputs to a set of already-distributed models.

其中核心代码在__call__函数中,此处分别按数据并行和句子并行进行了分布式的计算。

数据情况

先介绍下attention的数据维度,这里用的de-en翻译数据IWSLT,原语言是de,目标语言是en,de语vocab大小58947,en语vocab大小36323。
代码构造了一个dataset,其中包含几个变量:
src:输入语言的向量,shape=[batch_size, 输入句子长度],每一位是一个int数据,表示单词在词表里的idx
src_mask:输入语言的mask向量,shape=[batch_size, 输入句子长度],每一位是一个bool值,表示是否mask这个数据
trg:encode的输入向量,shape=[batch_size, 输出句子长度],每一位是一个int数据,表示单词在词表里的idx
trg_mask:输出语言的mask向量,shape=[batch_size, 输出句子长度],每一位是一个bool值,表示是否mask这个数据
trg:label,需要学习的向量(对应encode的输出向量),shape=[batch_size, 输出句子长度],每一位是一个int数据,表示单词在词表里的idx,就是trg向量中,每个单词向后挪一位(为了预测目标语言的下一个单词)

为什么会有trg和trg_y?
对于训练来说(Teaching Forcing模式),Decoder有一个输入和一个输出。比如句子” it is a good day “,输入会变成” it is a good day",而输出为"it is a good day "。对应到代码里,self.trg就是输入,而self.trg_y就是输出。接着对输入self.trg进行mask,使得Self-Attention不能访问未来的输入。这是通过make_std_mask函数实现的,这个函数会调用我们之前详细介绍过的subsequent_mask函数。最终得到的trg_mask的shape是(48/batch, 24, 24),表示24个时刻的Mask矩阵,这是一个对角线以及之下都是1的矩阵,前面已经介绍过了。

可以看真实训练的时候,src的mask所有的值都是True,但trg_mask只有当前能看到的词的mask才是True,看不到的都是False

并行化

其中,数据并行很好理解:将数据按batch拆分,分别送入不同的GPU进行推理,并计算返回值
句子并行:对句子进行拆分,将前后两段拆开,先对前面一段进行线性预测,利用得到的结果继续推理后一段。

举个例子:有两句话:

David Gallo: Das ist Bill Lange. Ich bin Dave Gallo.
Wir werden Ihnen einige Geschichten über das Meer in Videoform erzählen.
  • 第一句送到GPU0中,第二句话送到GPU1中,(这里是数据并行)
  • 先对:
David Gallo: Das ist Bill
Wir werden Ihnen einige Geschichten

进行预测,其中第一句的前半部分在GPU0中预测,第二句的前半部分在GPU1中预测。(这里是句子并行)
注意:其实句子并行化只在decoder输出之后进行,也就是只对线性推理部分进行了并行;
encoder-decoder的并行计算其实还是数据并行的,核心来自于:

    model_par = nn.DataParallel(model, device_ids=devices)

这一句话,详细关于DataParallel的介绍,可以参考https://zhuanlan.zhihu.com/p/102697821

相关资料:
https://zhuanlan.zhihu.com/p/118601295

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Transformer模型在训练时需要大量的计算资源。为了加速训练过程,可以使用分布式GPU进行并行训练。下面是使用PyTorch和Horovod库进行分布式GPU训练的示例代码: ```python import torch import torch.nn as nn import torch.optim as optim import horovod.torch as hvd from torch.utils.data import DataLoader # 初始Horovod hvd.init() # 设置GPU设备 torch.cuda.set_device(hvd.local_rank()) # 定义模型和数据加载器 model = Transformer().cuda() data_loader = DataLoader(dataset, batch_size=batch_size, shuffle=True) # 将模型放到所有可用的GPU上 model = nn.parallel.DistributedDataParallel(model, device_ids=[hvd.local_rank()]) # 定义优器和学习率调度器 optimizer = optim.Adam(model.parameters(), lr=0.001 * hvd.size()) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10) # 将优器与Horovod绑定 optimizer = hvd.DistributedOptimizer(optimizer, named_parameters=model.named_parameters()) # 开始训练 for epoch in range(num_epochs): for batch in data_loader: inputs, targets = batch inputs = inputs.cuda() targets = targets.cuda() optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() # 调整学习率 scheduler.step() # 在所有进程结束时进行反初始 hvd.shutdown() ``` 在这个示例代码中,首先使用Horovod库初始并设置GPU设备。然后将模型放到所有可用的GPU上,并使用Horovod库将优器与模型绑定。训练过程中,每个进程都加载一个batch的数据进行计算,并在所有进程结束时进行反初始。需要注意的是,使用Horovod库进行分布式GPU训练时,需要将学习率乘以进程数量进行调整,以保证训练的稳定性。 以上是一个简单的示例代码,实际应用中还需要根据具体的情况进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值