网站开发程序员自学wordpress收录查询

当前位置: 首页 > news >正文

网站开发程序员自学,wordpress收录查询,wordpress 搬站,黄页88推广效果怎么样近年来#xff0c;随着Transformer、MOE架构的提出#xff0c;使得深度学习模型轻松突破上万亿规模参数#xff0c;传统的单机单卡模式已经无法满足超大模型进行训练的要求。因此#xff0c;我们需要基于单机多卡、甚至是多机多卡进行分布式大模型的训练。 而利用AI集群随着Transformer、MOE架构的提出使得深度学习模型轻松突破上万亿规模参数传统的单机单卡模式已经无法满足超大模型进行训练的要求。因此我们需要基于单机多卡、甚至是多机多卡进行分布式大模型的训练。 而利用AI集群使深度学习算法更好地从大量数据中高效地训练出性能优良的大模型是分布式机器学习的首要目标。为了实现该目标一般需要根据硬件资源与数据/模型规模的匹配情况考虑对计算任务、训练数据和模型进行划分从而进行分布式存储和分布式训练。因此分布式训练相关技术值得我们进行深入分析其背后的机理。 下面主要对大模型进行分布式训练的并行技术进行讲解本系列大体分九篇文章进行讲解。 本文为分布式训练并行技术的第二篇数据并行。由于其原理相对比较简单因此在日常会应用中用的比较多。 技术交流 技术要学会分享、交流不建议闭门造车。一个人可以走的很快、一堆人可以走的更远。 相关资料、数据、技术交流提升均可加我们的交流群获取群友已超过2000人添加时最好的备注方式为来源兴趣方向方便找到志同道合的朋友。 方式①、添加微信号mlc2060备注来自CSDN 技术交流 方式②、微信搜索公众号机器学习社区后台回复加群 简述 所谓数据并行就是由于训练数据集太大因此将数据集分为N份每一份分别装载到N个GPU节点中同时每个GPU节点持有一个完整的模型副本分别基于每个GPU中的数据去进行梯度求导。然后在GPU0上对每个GPU中的梯度进行累加最后再将GPU0聚合后的结果广播到其他GPU节点。 注意这里是以GPU0作为参数服务器除此之外还可以使用CPU作为参数服务器。但是这种场景的训练速度通常会慢于使用GPU0作为参数服务器通常情况下GPU与CPU之间通信使用PCIe而GPU与GPU之间通信使用Nvlink。 当然还可以将参数服务器分布在所有GPU节点上面每个GPU只更新其中一部分梯度。 当然数据并行不仅仅指对训练的数据并行操作还可以对网络模型梯度、权重参数、优化器状态等数据进行并行。 下面主要以PyTorch中数据并行的发展为主线讲述现有一些数据并行方法。 数据并行PyTorch DP 数据并行(torch.nn.DataParallel)这是Pytorch最早提供的一种数据并行方式它基于单进程多线程进行实现的它使用一个进程来计算模型权重在每个批处理期间将数据分发到每个GPU。 DataParallel 的计算过程如下所示 将 inputs 从主 GPU 分发到所有 GPU 上。 将 model 从主 GPU 分发到所有 GPU 上。 每个 GPU 分别独立进行前向传播得到 outputs。 将每个 GPU 的 outputs 发回主 GPU。 在主 GPU 上通过 loss function 计算出 loss对 loss function 求导求出损失梯度。 计算得到的梯度分发到所有 GPU 上。 反向传播计算参数梯度。 将所有梯度回传到主 GPU通过梯度更新模型权重。 不断重复上面的过程。 它使用非常简单仅需一行代码即可实现。 net torch.nn.DataParallel(model, device_ids[0, 1, 2]) output net(input_var) # input_var can be on any device, including CPU但是它的缺点也很明显 单进程多线程带来的问题DataParallel使用单进程多线程进行实现的方便了信息的交换但受困于 GIL会带来性能开销速度很慢。而且只能在单台服务器单机多卡上使用不支持分布式。同时不能使用 Apex 进行混合精度训练。 效率问题主卡性能和通信开销容易成为瓶颈GPU 利用率通常很低数据集需要先拷贝到主进程然后再分片split到每个设备上权重参数只在主卡GPU0上更新需要每次迭代前向所有设备做一次同步每次迭代的网络输出需要聚集到主卡GPU0上。因此通信很快成为一个瓶颈。除此之外这将导致主卡和其他卡之间GPU利用率严重不均衡比如主卡使用了10G显存而其他卡只使用了2G显存batch size稍微设置大一点主卡的显存就OOM了。 不支持模型并行由于其本身的局限性没办法与模型并行组合使用。
当然目前PyTorch官方建议使用DistributedDataParallel而不是DataParallel类来进行多 GPU 训练即使在单机多卡的情况下。那么下面我们来看看PyTorch DDP。 分布式数据并行PyTorch DDP 分布式数据并行(torch.nn.DistributedDataParallel)基于多进程进行实现的每个进程都有独立的优化器执行自己的更新过程。每个进程都执行相同的任务并且每个进程都与所有其他进程通信。进程GPU之间只传递梯度这样网络通信就不再是瓶颈。 具体流程如下 首先将 rank0 进程中的模型参数广播到进程组中的其他进程 然后每个 DDP 进程都会创建一个 local Reducer 来负责梯度同步。 在训练过程中每个进程从磁盘加载 batch 数据并将它们传递到其 GPU。每个 GPU 都有自己的前向过程完成前向传播后梯度在各个 GPUs 间进行 All-Reduce每个 GPU 都收到其他 GPU 的梯度从而可以独自进行反向传播和参数更新。 同时每一层的梯度不依赖于前一层所以梯度的 All-Reduce 和后向过程同时计算以进一步缓解网络瓶颈。 在后向过程的最后每个节点都得到了平均梯度这样各个 GPU 中的模型参数保持同步 。 而DataParallel 是将梯度 reduce 到主卡在主卡上更新参数再将参数 broadcast 给其他 GPU这样无论是主卡的负载还是通信开销都比 DDP 大很多)相比于DataParallelDistributedDataParallel方式可以更好地进行多机多卡运算更好的进行负载均衡运行效率也更高虽然使用起来较为麻烦但对于追求性能来讲是一个更好的选择。 以下为DistributedDataParallel的简单示例使用 torch.nn.Linear 作为本地模型用 DDP 对其进行包装然后在 DDP 模型上运行一次前向传播、一次反向传播和更新优化器参数步骤。之后本地模型上的参数将被更新并且不同进程上的所有模型完全相同。 import torch import t dist import torch.multiprocessing as mp import torch.nn as nn import torch.optim as optim from torch.nn.parallel import DistributedDataParallel as DDPdef example(rank, world_size):# create default process groupdist.init_process_group(gloo, rankrank, world_sizeworld_size)# create local modelmodel nn.Linear(10, 10).to(rank)# construct DDP modelddp_model DDP(model, device_ids[rank])# define loss function and optimizerloss_fn nn.MSELoss()optimizer optim.SGD(ddp_model.parameters(), lr0.001)# forward passoutputs ddp_model(torch.randn(20, 10).to(rank))labels torch.randn(20, 10).to(rank)# backward passloss_fn(outputs, labels).backward()# update parametersoptimizer.step()def main():world_size 2mp.spawn(example,args(world_size,),nprocsworld_size,joinTrue)if namemain:# Environment variables which need to be# set when using c10ds default env# initialization mode.os.environ[MASTER_ADDR] localhostos.environ[MASTER_PORT] 29500main() DP 与 DDP 的区别 DP 和 DDP 的主要差异有以下几点 DP 是基于单进程多线程的实现只用于单机情况而 DDP 是多进程实现的每个 GPU 对应一个进程适用于单机和多机情况真正实现分布式训练并且因为每个进程都是独立的 Python 解释器DDP 避免了 GIL 带来的性能开销。 参数更新的方式不同。DDP在各进程梯度计算完成之后各进程需要将梯度进行汇总平均然后再由 rank0 的进程将其广播到所有进程后各进程用该梯度来独立的更新参数而 DP是梯度汇总到 GPU0反向传播更新参数再广播参数给其他剩余的 GPU。由于DDP各进程中的模型初始参数一致 (初始时刻进行一次广播)而每次用于更新参数的梯度也一致因此各进程的模型参数始终保持一致而在DP中全程维护一个 optimizer对各个GPU上梯度进行求平均而在主卡进行参数更新之后再将模型参数广播到其他GPU。相较于DPDDP传输的数据量更少训练更高效不存在 DP 中负载不均衡的问题。目前基本上 DP 已经被弃用。 DDP 支持模型并行而 DP 并不支持这意味如果模型太大单卡显存不足时只能使用DDP。
补充说明DP与DDP数据传输过程 DP数据传输过程 前向传播得到的输出结果gather到主cuda计算loss scatter上述loss到各个cuda 各个cuda反向传播计算得到梯度后gather到主cuda后主cuda的模型参数被更新。 主cuda将模型参数broadcast到其它cuda设备上至此完成权重参数值的同步。
综上DP大概是有4次输出传输。 DDP数据传输过程 前向传播的输出和loss的计算都是在每个cuda独立计算的梯度all-reduce到所有的CUDA(传输梯度)这样初始参数相同para.grad也相同反向传播后参数就还是保持一致的其他没有数据传输了。 完全分片数据并行(PyTorch FSDP) 由于 PyTorch FSDP 受 DeepSpeed ZeRO 启发而获得灵感因此下面先简要介绍下 ZeRO。 补充说明ZeRO 通常来说在模型训练的过程中GPU上需要进行存储的参数包括了模型本身的参数、优化器状态、激活函数的输出值、梯度以及一些零时的Buffer。各种数据的占比如下图所示 可以看到模型参数仅占模型训练过程中所有数据的一部分当进行混合精度运算时其中模型状态参数(优化器状态 梯度 模型参数占到了一大半以上。因此我们需要想办法去除模型训练过程中的冗余数据。 针对模型状态的存储优化去除冗余DeepSpeed 提出了 ZeROZeRO 使用的方法是分片即每张卡只存 1/N 的模型状态量这样系统内只维护一份模型状态参数。 ZeRO对 模型状态Model States参数进行不同程度的分割主要有三个不同级别 ZeRO-1 : 优化器状态分片 Optimizer States Sharding ZeRO-2 : 优化器状态与梯度分片Optimizer States Gradients Sharding ZeRO-3 : 优化器状态、梯度和模型权重参数分片Optimizer States Gradients Parameters Sharding
ZeRO-1 ZeRO-1没有将模型本身进行分片也没有将Gradient进行分片而是只将优化器进行分片。训练过程与DDP类似。 forward过程由每个rank的GPU独自完整的完成然后进行backward过程。在backward过程中梯度通过allReduce进行同步。 Optimizer state 使用贪心策略基于参数量进行分片以此确保每个rank几乎拥有相同大小的优化器内存。 每个rank只负责更新当前优化器分片的部分由于每个rank只有分片的优化器state所以当前rank忽略其余的state。 在更新过后通过广播或者allGather的方式确保所有的rank都收到最新更新过后的模型参数。
ZeRO-1 非常适合使用类似Adam进行优化的模型训练因为Adam拥有额外的参数mmomentum与vvariance特别是FP16混合精度训练。ZeRO-1 不适合使用SGD类似的优化器进行模型训练因为SGD只有较少的参数内存并且由于需要更新模型参数导致额外的通讯成本。ZeRO-1只是解决了Optimizer state的冗余。 ZeRO-2 相比于ZeRO-1ZeRO-2除了对optimizer state进行切分还对Gradient进行了切分。 像ZeRO-1一样将optimizer的参数进行分片并安排在不同的rank上。在backward过程中gradients被reduce操作到对应的rank上取代了all-reduce以此减少了通讯开销。每个rank独自更新各自负责的参数。在更新操作之后广播或allGather保证所有的ranks接收到更新后的参数。 ZeRO-3 为了进一步节省更多的内存ZeRO-3提出进行模型参数的分片。类似以上两种分片方式ranks负责模型参数的切片。可以进行参数切片的原因主要有以下两点 All-Reduce操作可以被拆分为Reduce与allgather操作的结合。 模型的每一层拥有该层的完整参数并且整个层能够直接被一个GPU装下。所以计算前向的时候除了当前rank需要的层之外其余的层的参数可以抛弃。从这个层面上来说Zero相当于数据并行模型并行。
FSDP 完全分片数据并行(torch.distributed.fsdp.FullyShardedDataParallel)是Pytorch最新的数据并行方案在1.11版本引入的新特性目的主要是用于训练大模型。我们都知道Pytorch DDP用起来简单方便但是要求整个模型加载到一个GPU上这使得大模型的训练需要使用额外复杂的设置进行模型分片。因此为了打破模型分片的障碍包括模型参数梯度优化器状态同时仍然保持了数据并行的简单性该新特性应运而生。 FSDP 是一种新型数据并行训练方法但与传统的数据并行不同传统的数据并行维护模型参数、梯度和优化器状态的每个 GPU 副本而 FSDP 将所有这些状态跨数据并行工作线程进行分片并且可以选择将模型参数分片卸载到 CPU。 下图显示了 FSDP 如何在 2 个数据并行进程中工作流程 通常模型层以嵌套方式用 FSDP 包装因此只有单个 FSDP 实例中的层需要在前向或后向计算期间将完整参数收集到单个设备。计算完成后收集到的完整参数将立即释放释放的内存可用于下一层的计算。通过这种方式可以节省峰值 GPU 内存从而可以扩展训练以使用更大的模型大小或更大的批量大小。为了进一步最大化内存效率当实例在计算中不活动时FSDP 可以将参数、梯度和优化器状态卸载到 CPU。 解锁ZeRO/FSDP的关键是我们可以把DDP之中的All-Reduce操作分解为独立的 Reduce-Scatter 和 All-Gather 操作。 image.png All-Reduce 是 Reduce-Scatter 和 All-Gather 的组合。聚合梯度的标准 All-Reduce 操作可以分解为两个单独的阶段。 Reduce-Scatter 阶段在每个GPU上会基于 rank 索引对 rank 之间相等的块进行求和。 All-Gather 阶段每个GPU上的聚合梯度分片可供所有GPU使用。
通过重新整理 Reduce-Scatter 和 All-Gather每个 DDP worker只需要存储一个参数分片和优化器状态。 在 PyTorch 中使用 FSDP 包装模型有两种方法。 自动包装Auto Wrapping是 DDP 的直接替代品 手动包装Manual Wrapping需要对模型定义代码进行少量的更改并且能够探索复杂的分片策略。
自动包装Auto Wrapping 模型层应以嵌套方式包装在 FSDP 中以节省峰值内存并实现通信和计算重叠。最简单的方法是自动包装它可以作为 DDP 的直接替代品而无需更改其余代码。 fsdp_auto_wrap_policy参数允许指定可调用函数以使用 FSDP 递归地包裹层。PyTorch FSDP提供的default_auto_wrap_policy函数递归地包裹参数数量大于100M的层。当然您也可以根据需要提供自己的包装策略。 此外可以选择配置 cpu_offload以便在计算中不使用包装参数时将这些参数卸载到 CPU。这可以进一步提高内存效率但代价是主机和设备之间的数据传输开销。 下面的示例展示了如何使用自动包装Auto Wrapping来包装 FSDP。 from torch.distributed.fsdp import (FullyShardedDataParallel,CPUOffload, ) from torch.distributed.fsdp.wrap import (default_auto_wrap_policy, ) import torch.nn as nnclass model(nn.Module):def init(self):super().init()self.layer1 nn.Linear(8, 4)self.layer2 nn.Linear(4, 16)self.layer3 nn.Linear(16, 4)model DistributedDataParallel(model()) fsdp_model FullyShardedDataParallel(model(),fsdp_auto_wrap_policydefault_auto_wrap_policy,cpu_offloadCPUOffload(offload_paramsTrue), )手动包装Manual Wrapping 通过有选择地对模型的某些部分应用包装手动包装对于探索复杂的分片策略非常有用。总体设置可以传递给enable_wrap()上下文管理器。 from torch.distributed.fsdp import (FullyShardedDataParallel,CPUOffload, ) from torch.distributed.fsdp.wrap import (enable_wrap,wrap, ) import torch.nn as nn from typing import Dictclass model(nn.Module):def init(self):super().init()self.layer1 wrap(nn.Linear(8, 4))self.layer2 nn.Linear(4, 16)self.layer3 wrap(nn.Linear(16, 4))wrapper_kwargs Dict(cpu_offloadCPUOffload(offload_paramsTrue)) with enable_wrap(wrapper_clsFullyShardedDataParallel, **wrapper_kwargs):fsdp_model wrap(model())使用上述两种方法之一用 FSDP 包装模型后可以采用与本地训练类似的方式训练模型具体如下所示 optim torch.optim.Adam(fsdp_model.parameters(), lr0.0001) for sample, label in next_batch():out fsdp_model(input)loss criterion(out, label)loss.backward()optim.step()DDP 与 FSDP 的区别 在标准的数据并行DistributedDataParallel训练方法中每个GPU上都有一个模型副本向前和向后传递的序列只在自己的数据分片上进行运行。在这些局部计算之后每个局部过程的参数和优化器与其他GPU共享以便计算全局权重更新。 而在FullyShardedDataParallel训练方法中 Model shard每个GPU上仅存在模型的分片。 All-gather每个GPU通过all-gather从其他GPU收集所有权重以在本地计算前向传播。 Forwardlocal在本地进行前向操作。前向计算和后向计算都是利用完整模型。 All-gather然后在后向传播之前再次执行此权重收集。 Backwardlocal本地进行后向操作。前向计算和后向计算都是利用完整模型此时每个GPU上也都是全部梯度。 Reduce-Scatter在向后传播之后局部梯度被聚合并且通过 Reduce-Scatter 在各个GPU上分片每个分片上的梯度是聚合之后本分片对应的那部分。 Update Weightlocal每个GPU更新其局部权重分片。
同时为了最大限度地提高内存效率我们可以在每层前向传播后丢弃全部权重为后续层节省内存。这可以通过将 FSDP 包装应用于网络中的每一层来实现通过设置reshard_after_forwardTrue。 总结 本文主要讲解了大模型分布式训练并行技术的数据并行并以Pytorch为主线讲解了DP、DDP、FSDP三种不同的数据并行方案。 DP 主要存在如下问题 单进程多线程模式由于锁的机制导致线程间同步存在瓶颈。 使用普通的All-Reduce机制所有的卡需要将梯度同步给0号节点并由0号节点平均梯度后反向传播再分发给所有其他节点意味着0号节点负载很重。 由于第二点的原因导致0号GPU通讯成本是随着GPU数量的上升而线性上升的。 不支持多机多卡。
目前由于性能问题DP基本不用了。 而 DDP 是多进程实现的每个 GPU 对应一个进程适用于单机和多机情况真正实现分布式训练并且因为每个进程都是独立的 Python 解释器DDP 避免了 GIL 带来的性能开销。 DDP在各进程梯度计算完成之后各进程需要将梯度进行汇总平均然后再由 rank0 的进程将其广播到所有进程后各进程用该梯度来独立的更新参数。由于DDP各进程中的模型初始参数一致 (初始时刻进行一次广播)而每次用于更新参数的梯度也一致因此各进程的模型参数始终保持一致。相较于DPDDP传输的数据量更少训练更高效不存在 DP 中负载不均衡的问题。 虽然Pytorch DDP实现了真正的分布式训练同时避免了DP 中负载不均衡的问题但是要求整个模型加载到一个GPU上这使得大模型的训练需要使用额外复杂的设置进行模型分片。因此为了打破模型分片的障碍包括模型参数梯度优化器状态同时仍然保持了数据并行的简单性FSDP应运而生。 FSDP 是一种新型数据并行训练方法但与传统的数据并行不同传统的数据并行维护模型参数、梯度和优化器状态的每个 GPU 副本而 FSDP 将所有这些状态跨数据并行工作线程进行分片并且可以选择将模型参数分片卸载到 CPU。 如果觉得我的文章能够能够给您带来帮助期待您的点赞收藏加关注~~