首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >大模型的实践应用-大语言模型的分布式训练并行策略,数据并行原理

大模型的实践应用-大语言模型的分布式训练并行策略,数据并行原理

原创
作者头像
微学AI
发布2025-05-29 11:39:10
发布2025-05-29 11:39:10
3410
举报

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用14-大语言模型的分布式训练并行策略,数据并行原理。大语言模型的分布式训练并行策略主要通过数据并行来实现。数据并行是指将训练数据划分为多个小批量, 然后将这些小批量分配给不同的计算设备进行并行处理。通过数据并行的并行策略,每个计算设备都可以独立地计算小批量数据的梯度,并将结果进行聚合,从而实现模型的并行训练。这种分布式训练策略可以加速大语言模型的训练过程,并提高模型的性能和效果。

一、大模型分布式训练背景

随着语言模型的参数量和所需训练数据量增长,单个机器的资源已经无法满足需求。这时就需要设计一种分布式训练系统来克服计算和内存资源的限制。分布式训练系统将一个模型训练任务拆分成多个子任务,并将这些子任务分发给多台计算设备,从而解决资源瓶颈。想象一下,如果我们要建造一个大型拼图,但只有一台机器和有限的时间,很难在规定时间内完成拼图。这时,我们可以雇佣一群工人,每个工人负责拼接一部分拼图,然后将他们的工作合并起来,最终完成整个拼图。在分布式训练中,每台计算设备就像一个工人,负责处理模型训练的一部分,然后将结果汇总起来得到完整的训练模型。 为了利用数万计算加速芯片的集群,训练大规模语言模型,我们需要考虑集群架构、并行策略、模型架构、内存优化和计算优化等技术。例如,我们可以将集群中的计算设备按照某种拓扑结构连接起来,使得数据传输更高效。同时,我们可以将模型的参数分割成多个小块,在不同设备上并行计算,加快训练速度。此外,还可以使用一些优化技术,如减少数据传输量、减少内存占用等,以提高分布式训练的效率。 假设我们要训练一个巨大的语言模型,就像组装一幅巨大的拼图。我们可以将拼图分成许多小块,然后雇佣一批工人来同时拼接这些小块。每个工人只需处理自己负责的小块,并与其他工人交流合作,最终完成整个拼图。这样,我们就能够利用集群中的计算资源,通过分布式训练系统,高效地训练大规模语言模型。本章将以 DeepSpeed 为例介绍如何在集群上训练大语言模型。

二、分布式训练介绍

分布式训练是指将机器学习或深度学习模型训练任务分解成多个子任 务,并在多个计算设备上并行地进行训练。图4.1给出了单个计算设备和多个计算设备的示例,这 里计算设备可以是中央处理器(CPU)、图形处理器(GPU)、张量处理器(TPU)也可以是神经网络处理器(NPU) 当我们谈论分布式训练时,可以把它想象成一家工厂里的生产线。假设你有一台机器需要制造,传统上这项任务是由一台机器完成的。但是现在,你拥有了多台机器,每台都可以独立工作。为了更快地完成生产,你决定将这些机器连接起来,分别负责生产线上的不同部分。 在分布式训练中,就好像你有了多台机器或者多个艺术家来共同完成任务。每个计算设备犹如一个艺术家,负责处理部分数据或者模型的训练,最后将它们的成果合并在一起,就像将每个艺术家的画作组合成完整的画作一样。这样一来,整个训练过程就能更快速地完成。 机器学习模型的迅速进展带来了对计算资源的巨大需求。从2013年的AlexNet到2022年的PalM模型,参数数量爆炸性增长了5400亿,这种增长速度大致每18个月就翻56倍。随着模型参数量的增加,所需的训练数据量也在急剧扩大,这自然导致了对计算能力需求的激增。尽管如此,近几年来CPU的性能提升速度已经慢于摩尔定律所预测的每18个月翻倍的速度。尽管GPU、TPU等计算加速器为机器学习任务提供了大量的计算能力,但它们的增长速度也没有达到摩尔定律所预测的翻倍速度。因此,为了跟上机器学习模型计算需求的增长,必须采用分布式训练系统,这种系统可以集合并利用多台设备的计算能力,以满足模型训练日益增长的需求。 大型语言模型的训练需要巨大的计算资源,因此分布式训练系统成为了必要的选择。例如,GPT-3模型的训练使用了大量的NVIDIA V100 GPU,而OPT模型则使用了992块NVIDIA A100 80G GPU。这些模型使用了全分片数据并行(Fully Shared Data Parallel)和Megatron-LM张量并行(Tensor Parallelism)技术来提高训练效率。BLOOM模型的训练则使用了48个计算节点,每个节点包含8块NVIDIA A100 80G GPU,并且使用了基于Omni-Path 100 Gbps网卡构建的增强8维超立方体全局拓扑网络进行节点间的通信。LLaMA模型系列的训练同样使用了NVIDIA A100 GPU,其中LLaMA-7B模型训练需要82432 GPU小时,LLaMA-13B模型训练需要135168 GPU小时,而LLaMA-33B模型训练则需要530432 GPU小时。由于LLaMA模型使用的训练数据量远超OPT和BLOOM模型,尽管其模型参数量可能小于这两个模型,但所需的计算量仍然非常惊人。

三、分布式训练并行策略

分布式训练系统的核心宗旨在于把原本在单个节点上执行的模型训练转换成在多个节点上并行执行的训练过程。对于大型语言模型而言,训练的本质是依托数据和损失函数,通过优化算法对神经网络的参数进行调整和优化。在单个节点的模型训练系统中,系统结构主要由数据部分和模型部分两大块组成。训练通常通过处理多个数据批次(Mini-batch)来完成。在这些批次中,每一个批次代表了一组数据。训练系统利用这些数据批次,结合损失函数和优化算法来计算梯度,进而对模型的参数进行更新和优化。对于大语言模型中的多层神经网络执行过程,可以通过计算图来形象表示。这个计算图由众多互连的算子构成,每一个算子对应于神经网络中的一层,而参数则代表在这些层的训练过程中需要更新的权重。 计算图的执行过程主要分为两个阶段:前向传播和反向传播。在前向传播阶段,数据从第一个操作开始依次经过各个操作,最终得到输出结果;这个过程会一直持续到图中的最后一个操作。在反向传播阶段,根据损失函数和优化算法,从最后一个操作开始反向计算每个操作的梯度,并利用这些梯度来更新操作中的参数。当一个数据批次的前向和反向传播完成后,系统会继续处理下一个数据批次,不断迭代更新模型参数。对于单设备模型训练系统,若要进行并行加速,可以从数据和模型两个方面入手。一方面,可以将数据分割并发送到多个设备上,每个设备运行相同的模型副本,这种方式称为数据并行。另一方面,可以将模型的不同部分分散到多个设备上执行,这种方式称为模型并行。在训练大型语言模型时,通常需要同时采用数据和模型的分割策略,以实现更高效的并行计算,这种方式称为混合并行。

3.1数据并行

在数据并行的训练设置中,每个参与计算的节点上都保存有神经网络模型的完整副本。在每一次迭代过程中,每个节点仅接收训练数据集的一个子集,该子集包含\frac{N}{M}个样本,其中$N$是批次中的总样本数,而M是计算节点的数量。接着,每个节点利用其接收到的样本子集执行模型的前向传播。 完成前向传播后,每个节点都会基于其本地样本计算损失函数并进而得到梯度G_i,这里的i代表节点的编号。随后,这些本地梯度G_i会被广播到所有节点。每个节点需要收集其他所有节点计算出的梯度,然后计算这些梯度的总和,即\sum_{i=1}^{N} G_i,再将其除以总样本数$N$以得到平均梯度。最终,使用这个平均梯度对模型进行参数更新,从而完成这一批次的训练。 使用 PyTorch DistributedDataParallel 可以实现单个服务器多加速卡训练,代码如下:

代码语言:txt
复制
import math
import argparse
import os
import shutil
import time
import warnings
import numpy as np
warnings.filterwarnings('ignore')
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.backends.cudnn as cudnn
import torch.distributed as dist
import torch.optim
import torch.utils.data
import torch.utils.data.distributed
from torch.utils.data.distributed import DistributedSampler
from models import DeepLab
from dataset import Cityscaples

class DistributedSampler(Sampler):
    def __init__(self, dataset, num_replicas=None, rank=None, shuffle=True, seed=0):
        if num_replicas is None:
            if not dist.is_available():
                raise RuntimeError("Requires distributed package to be available")
            num_replicas = dist.get_world_size()
        if rank is None:
            if not dist.is_available():
                raise RuntimeError("Requires distributed package to be available")
            rank = dist.get_rank()
        self.dataset = dataset # 数据集
        self.num_replicas = num_replicas # 进程个数 默认等于 world_size(GPU 个数)
        self.rank = rank # 当前属于哪个进程/哪块 GPU
        self.epoch = 0
        self.num_samples = int(math.ceil(len(self.dataset) * 1.0 / self.num_replicas))
        # 每个进程的样本个数
        self.total_size = self.num_samples * self.num_replicas # 数据集总样本的个数
        self.shuffle = shuffle # 是否要打乱数据集
        self.seed = seed
    def __iter__(self):
        # 1、 Shuffle 处理:打乱数据集顺序
        if self.shuffle:
            # 根据 epoch 和种子进行混淆
            g = torch.Generator()
            # 这里 self.seed 是一个定值,通过 set_epoch 改变 self.epoch 可以改变我们的初始化种子
            # 这就可以让每一个 epoch 中数据集的打乱顺序不同,使每一个 epoch 中,
            # 每一块 GPU 拿到的数据都不一样,这样可以有利于更好的训练
            g.manual_seed(self.seed + self.epoch)
            indices = torch.randperm(len(self.dataset), generator=g).tolist()
        else:
            indices = list(range(len(self.dataset)))
        # 数据补充
        indices += indices[:(self.total_size - len(indices))]
        assert len(indices) == self.total_size
        # 分配数据
        indices = indices[self.rank:self.total_size:self.num_replicas]
        assert len(indices) == self.num_samples
        return iter(indices)
        def __len__(self):
        return self.num_samples

    def set_epoch(self, epoch):
        self.epoch = epoch
        
parser = argparse.ArgumentParser(description='DeepLab')
parser.add_argument('-j', '--workers', default=4, type=int, metavar='N',
help='number of data loading workers (default: 4)')
parser.add_argument('--epochs', default=100, type=int, metavar='N',
help='number of total epochs to run')
parser.add_argument('--start-epoch', default=0, type=int, metavar='N',
help='manual epoch number (useful on restarts)')
parser.add_argument('-b', '--batch-size', default=3, type=int,
metavar='N')
parser.add_argument('--local_rank', default=0, type=int, help='node rank for distributed training')
args = parser.parse_args()
torch.distributed.init_process_group(backend="nccl") # 初始化
print("Use GPU: {} for training".format(args.local_rank))
# create model
model = DeepLab()
torch.cuda.set_device(args.local_rank) # 当前显卡
model = model.cuda() # 模型放在显卡上

model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank],
output_device=args.local_rank, find_unused_parameters=True) # 数据并行
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.SGD(model.parameters(), args.lr,
momentum=args.momentum, weight_decay=args.weight_decay)
train_dataset = Cityscaples()
train_sampler = DistributedSampler(train_dataset) # 分配数据
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=args.batch_size,
shuffle=False, num_workers=args.workers, pin_memory=True, sampler=train_sampler)

3.2 模型并行

模型并行是一种技术,用于解决单个计算节点内存限制的问题,特别是在处理大型模型时。以 GPT-3 为例,一个拥有 1750 亿个参数的模型,如果每个参数都用 32 位浮点数表示,那么它将需要 700GB 的内存。即使使用 16 位浮点数,每个模型副本也需要 350GB。然而,即使是 2022 年 3 月 NVIDIA 推出的 H100 加速卡,其显存也仅为 80GB,不足以容纳整个模型。 为了克服这一限制,模型并行可以采用两种不同的方法来分割计算图: 1. 层间并行或算子间并行(Inter-operator Parallelism),也称为流水线并行(Pipeline Parallelism, PP):在这种方法中,模型的不同层被分配到不同的设备上执行。每个设备负责计算一部分层,然后将结果传递给下一个设备,就像流水线上的不同工作站一样。 2. 层内并行或算子内并行:在这种方法中,同一个层的不同参数被分配到不同的设备上进行计算。这种方法称为张量并行,因为它是通过将张量(即参数)分配到不同的设备上来实现的。 模型并行是一种在多个设备上分割和执行大型模型的技术,以利用每个设备的内存和计算资源,从而允许模型的大小超出单个设备的容量限制。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、大模型分布式训练背景
  • 二、分布式训练介绍
  • 三、分布式训练并行策略
  • 3.1数据并行
    • 3.2 模型并行
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档