DDP(DistributedDataParallel) 分布式训练2——原理与实践

1 分布式编程

一个分布式系统,相对于单机系统,其最大的特征就是,其数据、处理是分布在不同地方的。与此相伴的是,各节点间有交换数据的需求,为此需要定义交换数据的规范、接口。在此基础上,才能构建起分布式计算的大框架。例如google大数据三驾马车之一的map-reduce概念,简要地描述,就是将数据分开成N份map到N个地方,并行进行处理;处理完成后,再将结果reduce到一起。

为了满足分布式编程的需求,PyTorch提供了一些分布式基本接口,在torch.distributed中。torch.distributed代码文档

1.1 all_reduce

所谓的reduce,就是不同节点各有一份数据,把这些数据汇总到一起。在这里,我们规定各个节点上的这份数据有着相同的shape和data type,并规定汇总的方法是相加。简而言之,就是把各个节点上的一份相同规范的数据相加到一起。

所谓的all_reduce,就是在reduce的基础上,把最终的结果发回到各个节点上。

具体的allreduce实现,要看具体的backend。流行的GPU backend NCCL,all_reduce的实现使用了ring思想。

DDP利用all_reduce,来进行不同进程上的梯度的平均操作。PyTorch提供了几个all_reduce的版本,下面这个就是Ring-Reduce版本:

def all_reduce(tensor,op=ReduceOp.SUM,group=group.WORLD,async_op=False):"""Reduces the tensor data across all machines in such a way that all getthe final result.After the call ``tensor`` is going to be bitwise identical in all processes.Arguments:tensor (Tensor): Input and output of the collective. The functionoperates in-place.op (optional): One of the values from``torch.distributed.ReduceOp``enum.  Specifies an operation used for element-wise reductions.group (ProcessGroup, optional): The process group to work onasync_op (bool, optional): Whether this op should be an async opReturns:Async work handle, if async_op is set to True.None, if not async_op or if not part of the group"""

2 pytorch 数据结构

2.1 buffer

在PyTorch中,所有的模型都会继承module类。可以说,一个CNN模型,其就是由一系列module嵌套组合而成的。要了解模型,就必须从module下手。下面是module的初始化代码,可以看到,它定义了一系列变量。

# torch.nn.modules.py. line 71. Class module:def __init__(self):"""Initializes internal Module state, shared by both nn.Module and ScriptModule."""torch._C._log_api_usage_once("python.nn_module")self.training = Trueself._parameters = OrderedDict()self._buffers = OrderedDict()self._backward_hooks = OrderedDict()self._forward_hooks = OrderedDict()self._forward_pre_hooks = OrderedDict()self._state_dict_hooks = OrderedDict()self._load_state_dict_pre_hooks = OrderedDict()self._modules = OrderedDict()

module的基本要素可以分为2组,一组是状态,一组是各种各样的hooks。

状态有以下4个东西:

  • self.training
    指的是网络是否在训练状态中。
  • self._modules
    modules是下属的模块,相当于迭代地定义了self.trainig, self._modules, self._parameters等一系列变量
  • self._parameters
    指的是网络的参数
  • self._buffers
    不是参数,但也对网络很重要,会被持久化保存的数据。

例如,BatchNorm中的moving mean and variance就是buffer,其优化不是通过梯度反向传播而是通过其他途径。

从本质上讲,当一个模型的网络结构被定义后,其状态就是由parameter和buffer的迭代组合表示的。当我们保存模型,调用model.staic_dict()的时候,我们同时会得到模型的parameter和buffer。

在DDP中,如果我们要在不同进程中维持相同的状态,我们不光要传递parameter的梯度,也要传递buffer。事实上,DDP就是这么做的。当每次网络传播开始前,其都会把master节点上的buffer广播给其他节点,维持状态的统一。

2.2 hook

hook提供了这么一种机制:程序提供hook接口,用户可以写一个hook函数,然后钩在hook接口,即程序的主体上从而可以插入到中间执行。DDP使用hook技术把自己的逻辑插入到module的训练过程中去。

在模型训练时,各个进程通过一种叫Ring-Reduce的方法与其他进程通讯,从而获得所有进程的梯度。PyTorch提供了很多个hook接口,可以用来把 Ring-Reduce 机制插入到 module 中。

parameter在反向梯度计算结束后提供了一个hook接口。DDP把Ring-Reduce的代码写成一个hook函数,插入到这里。每次parameter的反向梯度计算结束后,程序就会调用这个hook函数,从而开启Ring-Reduce流程。因为所有模型都用到parameter,所以DDP模型用hook函数就解决了所有模型的梯度平均问题。

2.2.1 torch.nn.parameter

torch.nn.parameter是torch.Tensor上的一层概念封装,hook机制也是定义在torch.Tensor中的。

2.2.2 torch.tensor.Tensor

DDP的关键代码(即梯度平均)是用C++实现的。但是,在C++、python代码中Tensor都给出了hook接口,实现相似的功能。

Tensor的python hook接口:

# line 200. Class Tensor.def register_hook(self, hook):r"""Registers a backward hook.The hook will be called every time a gradient with respect to theTensor is computed. The hook should have the following signature::hook(grad) -> Tensor or NoneThe hook should not modify its argument, but it can optionally returna new gradient which will be used in place of :attr:`grad`.This function returns a handle with a method ``handle.remove()``that removes the hook from the module.Example::>>> v = torch.tensor([0., 0., 0.], requires_grad=True)>>> h = v.register_hook(lambda grad: grad * 2)  # double the gradient>>> v.backward(torch.tensor([1., 2., 3.]))>>> v.grad246[torch.FloatTensor of size (3,)]>>> h.remove()  # removes the hook"""

3 DDP 内部实现

3.1 DDP 代码文档链接:

https://github.com/pytorch/pytorch/blob/v1.5.0/torch/nn/parallel/distributed.py
https://github.com/pytorch/pytorch/blob/v1.5.0/torch/csrc/distributed/c10d/reducer.h
https://github.com/pytorch/pytorch/blob/v1.5.0/torch/distributed/distributed_c10d.py
https://pytorch.org/docs/stable/notes/ddp.html

3.2 DDP 模式
3.2.1 准备阶段
  • 环境准备(就是init_process_group这一步)。各个进程会在这一步,与master节点进行握手,建立连接。
    • 如果连接上的进程数量不足约定的 word_size,进程会一直等待。也就是说,如果你约定了world_size=64,但是只开了6台8卡机器,那么程序会一直暂停在这个地方。
  • DDP初始化(也就是model = DDP(model)这一步)
    • 把parameter,buffer从master节点传到其他节点,使所有进程上的状态一致。
    • DDP通过这一步保证所有进程的初始状态一致。所以,请确保在这一步之后,代码不会再修改模型的任何东西,包括添加、修改、删除parameter和buffer
    • 如果每个节点有多卡,则在每张卡上创建模型(类似DP)
  • 把parameter进行分组,每一组称为一个bucket。临近的parameter在同一个bucket。
    • 这是为了加速,在梯度通讯时,先计算、得到梯度的bucket会马上进行通讯,不必等到所有梯度计算结束才进行通讯。
  • 创建管理器reducer,给每个parameter注册梯度平均的hook。
    • 这一步的具体实现是在C++代码里面的,即reducer.h文件。
  • 为可能的SyncBN层做准备
3.2.2 正式训练阶段

在每个step中,DDP模型都会做下面的事情:

  • 采样数据,从dataloader得到一个batch的数据,用于当前计算(for data, label in dataloader)。
    • 因为我们的dataloader使用了DistributedSampler,所以各个进程之间的数据是不会重复的。如果要确保DDP性能和单卡性能一致,这边需要保证在数据上,DDP模式下的一个epoch和单卡下的一个epoch是等效的。
  • 进行网络的前向计算(prediction = model(data))
    • 同步各进程状态
      • 对单进程多卡复制模式,要在进程内同步多卡之间的parameter和buffer
    • 同步各进程之间的buffer。
    • 进行真正的前向计算
    • 当DDP参数find_unused_parameter为true时,其会在forward结束时,启动一个回溯,标记出所有没被用到的parameter,提前把这些设定为ready。
      • find_unused_parameter的默认值是false,因为其会拖慢速度。
  • 计算梯度(loss.backward())
    • reducer外面:各个进程各自开始反向地计算梯度。
      • 梯度是反向计算的,所以最后面的参数反而是最先得到梯度的。
    • reducer外面:当某个parameter的梯度计算好了的时候,其之前注册的grad hook就会被触发,在reducer里把这个parameter的状态标记为ready。
    • reducer里面:当某个bucket的所有parameter都是ready状态时,reducer会开始对这个bucket的所有parameter都开始一个异步的all-reduce梯度平均操作。
      • bucket的执行过程也是有顺序的,其顺序与parameter是相反的,即最先注册的parameter的bucket在最后面。
      • 在创建module的时候,务必把先进行计算的parameter注册在前面,后计算的在后面。不然,reducer会卡在某一个bucket等待,使训练时间延长
        • 所谓的参数注册,其实就是创建网络层。也就是要求按照网络计算顺序,依次创建网络层。
    • reducer里面:当所有bucket的梯度平均都结束后,reducer才会把得到的平均grad结果正式写入到parameter.grad里面。
  • 优化器optimizer应用gradient,更新参数(optimizer.step())。
    • 这一步,是和DDP没关系的。

虽然DDP的实现代码与optimizer没有关系,但是关于optimizer有个额外的东西需要说明。更新后的参数最终能在各进程间保持一致,是由以下因素保证的:

  • 参数初始值相同
  • 参数更新值相同
    • 更新值相同又是由以下因素保证的:
      • optimizer初始状态相同
      • 每次opimizer.step()时的梯度相同。

因为optimizer和DDP是没有关系的,所以optimizer初始状态的同一性是不被DDP保证的!

大多数官方optimizer,其实现在能保证从同样状态的model初始化时,其初始状态是相同的。所以这边我们只要保证在DDP模型创建后才初始化optimizer,就不用做额外的操作。但是,如果自定义optimizer,则需要自己来保证其统一性!

3.2.3 性能没有提升的可能原因
  • 检查是否按照单进程单卡的模式启动
  • 检查是否使用的是 NCCL 的后端
  • 保证不同进程里的模型都是相同结构的;保证parameter(你可以理解为网络层)的创建顺序是一致的。
  • 检查是否模型的parameter创建顺序与真实计算顺序一致
  • 不允许在产生DDP后,新增、减少、随机修改、替换参数,会造成梯度reduce出错、各进程间的参数不相同、丢失hook机制
  • 检查DDP模式下的一个epoch的数据和单卡下的一个epoch的数据是否是等效
  • 检查初始状态的同一性:
    • parameter、buffer初始状态同一性
    • optimizer初始状态同一性
3.2.4 DistributedSampler机制

DistributedSampler能够 给不同进程分配数据集的不重叠、不交叉部分。每次epoch我们都会随机shuffle数据集,那么,不同进程之间要怎么保持shuffle后数据集的一致性呢?DistributedSampler的实现方式是,不同进程会使用一个相同的随机数种子,这样shuffle出来的东西就能确保一致。

DistributedSampler使用当前epoch作为随机数种子,从而使得不同epoch下有不同的shuffle结果。所以,每次epoch开始前都要调用一下sampler的set_epoch方法,这样才能让数据集随机shuffle起来。

DistributedSampler的核心源代码:

# line 56def __iter__(self):# deterministically shuffle based on epochg = torch.Generator()g.manual_seed(self.epoch)if self.shuffle:indices = torch.randperm(len(self.dataset), generator=g).tolist()else:indices = list(range(len(self.dataset)))# add extra samples to make it evenly divisibleindices += indices[:(self.total_size - len(indices))]assert len(indices) == self.total_size# subsampleindices = indices[self.rank:self.total_size:self.num_replicas]assert len(indices) == self.num_samplesreturn iter(indices)
# line 79def set_epoch(self, epoch):self.epoch = epoch

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/709250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

unity使用Registry类将指定内容写入注册表

遇到一个新需求,在exe执行初期把指定内容写入注册表,Playerprefs固然可以写入,但是小白不知道怎么利用Playerprefs写入DWORD类型的数据,因此使用了Registry类 一. 对注册表中键的访问 注册表中共可分为五类 一般在操作时&#…

Lombok的@Slf4j注解使用并集成Logback日志框架调试代码

1. 概述 我们平时在写代码的时候,除了IDE断点的形式以外,还会用到System.out.println在控制台中输出相关的调试信息。本文介绍了在控制台中输出调试信息的另一种实现方式,使用Logback日志框架可以在控制台中用自定义的格式,输出更…

代码随想录算法训练营第35天 | 435. 无重叠区间 ,763.划分字母区间 , 56. 合并区间

贪心算法章节理论基础: https://programmercarl.com/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 435. 无重叠区间 题目链接:https://leetcode.cn/problems/non-overlapping-intervals/ 思路: 相信…

2024年腾讯云服务器优惠政策整理,大降价!

腾讯云服务器多少钱一年?62元一年起,2核2G3M配置,腾讯云2核4G5M轻量应用服务器218元一年、756元3年,4核16G12M服务器32元1个月、312元一年,8核32G22M服务器115元1个月、345元3个月,腾讯云服务器网txyfwq.co…

【两颗二叉树】【递归遍历】【▲队列层序遍历】Leetcode 617. 合并二叉树

【两颗二叉树】【递归遍历】【▲队列层序遍历】Leetcode 617. 合并二叉树 解法1 深度优先 递归 前序解法2 采用队列进行层序遍历 挺巧妙的可以再看 ---------------🎈🎈题目链接🎈🎈------------------- 解法1 深度优先 递归 前…

开关量传感器与LoRa技术结合,实现智能鱼塘养殖监控方案

开关量传感器与LoRa技术结合,可以为智能鱼塘养殖提供高效、实时的监控方案。通过部署开关量传感器和LoRa通信设备,可以实现对鱼塘水质、水位、氧气含量等关键参数的监测和远程管理,为养殖场主提供及时准确的数据支持,帮助其科学管…

Docker使用数据卷自定义镜像Dockerfile

目录 一、数据卷 1.1、简介 1.2、用途 1.3、特性: 1.4、数据卷相关操作 1.5、使用情况 二、自定义镜像Dockerfile 2.1、Dockerfile 2.1、使用情况 2.3、具体操作 a、自定义centos 创建文件 编辑内容 制作镜像 测试 b、自定义tomcat 创建文件 编…

Windows安装VNC连接工具并结合cpolar实现远程内网Ubuntu系统桌面

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…

4.1 统计学基本概念

1、统计学含义 统计学是一门收集、处理、分析、解释数据并从数据中得出结论的科学 统计学的对象是数据。 数据的形式:分数字和文字。 2、数据分析步骤 收集数据——处理数据——分析数据——解释数据 数据收集——取得统计数据数据处理——将数据用图表等形式展…

qml 项目依赖

文章目录 出现的问题最终对比下一步 把 apptestQml3_6.exe 放到一个单独目录下,执行 windeployqt.exe ./apptestQml3_6.exe但是出了很多问题,根本运行不起来。 但是在release目录下执行下,程序能跑起来。 根据错误提示,进行添加。…

存储多路径之linux multipath

一、何为multipath 普通的电脑主机都是一个硬盘挂接到一个总线上,这里是一对一的关系。而到了有光纤组成的SAN环境,由于主机和存储通过了光纤交换机连接,这样的话,就构成了多对多的关系。也就是说,主机到存储可以有多…

C#,动态规划(DP)金矿问题(Gold Mine Problem)的算法与源代码

1 金矿问题(Gold Mine Problem) 给定一个N*M尺寸的金矿,每个点都有一个非负数表示当前点所含的黄金数目,最开始矿工位于第一列,但是可以位于任意行。矿工只能向右,右上,右下三个方向移动。问该…

如何在Linux使用Docker部署Nexus容器并实现公网访问本地仓库【内网穿透】

文章目录 1. Docker安装Nexus2. 本地访问Nexus3. Linux安装Cpolar4. 配置Nexus界面公网地址5. 远程访问 Nexus界面6. 固定Nexus公网地址7. 固定地址访问Nexus Nexus是一个仓库管理工具,用于管理和组织软件构建过程中的依赖项和构件。它与Maven密切相关,可…

柏映国研究员出席新型饲用酶制剂研究与产品开发演讲

演讲嘉宾:柏映国 研究员 中国农科院北京畜牧兽医研究所 演讲题目:新型饲用酶制剂研究与产品开发 简介:柏映国,2004年毕业于江南大学,获发酵工程专业工学硕士学位,2010年毕业于中国农业科学院研究生院&a…

Windows虚拟主机如何开启网页debug模式

前不久,有客户咨询想要知道如何开启网页debug模式,以便后期他网站出现异常可以自行排查。这边了解到他当前使用的是Hostease 的Windows 虚拟主机,而开启网页debug模式的操作步骤如下: 1.Hostease的Windows虚拟主机都是带Plesk面板的,因此需要…

HarmonyOS—开发云数据库

您可以在云侧工程下开发云数据库资源,包括创建对象类型、在对象类型中添加数据条目、部署云数据库。 创建对象类型 对象类型(即ObjectType)用于定义存储对象的集合,不同的对象类型对应的不同数据结构。每创建一个对象类型&#…

Java底层自学大纲_分布式篇

分布式专题_自学大纲所属类别学习主题建议课时(h)A 分布式锁001 Zookeeper实现分布式锁l-常规实现方式2.5A 分布式锁002 Zookeeper实现分布式锁II-续命&超时&羊群效应问题解决方案2.5A 分布式锁003 Zookeeper实现分布式锁III-基于Curator框架实现…

盘点6个最受欢迎的 Vue.js UI 库

在2024年,随着Vue.js的不断普及和发展,这个轻量级、易于学习的JavaScript框架在前端开发者中的受欢迎程度日益上升。Vue.js之所以受到青睐,很大一部分原因是其庞大的生态系统,特别是众多的UI库,这些库提供了预先构建的…

Cadence Allegro PCB设计88问解析(三十四) 之 Allegro 中 DDR等长处理

一个学习信号完整性仿真的layout工程师 在进行PCB设计时 ,会遇到一些单端的信号要做等长处理,比如DDR的数据线,交换机之间的数据线之类的。这时需要我们建立match group,来做等长。下面简单介绍在Allegro中怎么做等长:…

【c语言】探索联合和枚举---解锁更多选择

前言 上一篇 讲解的是结构体相关知识,接着本篇主要讲解的是 联合和枚举 相关知识 结构体、联合体和枚举都属于 自定义类型。 那么接下来就跟上我的节奏,准备发车~ 欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误&#xf…