上篇文章【[论文品鉴] DeepSeek V3 最新论文 之 DeepEP】 介绍了分布式并行策略中的EP,简单的提到了其他几种并行策略,但碍于精力和篇幅限制决定将内容分几期,本期首先介绍DP,但并不是因为DP简单,相反DP的水也很深,例如:“DP到底同步的是什么数据?怎么同步的?“,“AllReduce/Ring-AllReduce是什么?”,“ZeRO1、2、3又都是什么?” 等各种问题,会结合PyTorch代码,尽量做到详细由浅入深。

单机单卡

在深入分布式并行策略前,先回顾一下单机单卡的训练模式:

在这里插入图片描述

  1. CPU 加载数据,并将数据分成 batch 批次
  2. CPUbatch 批次数据传给 GPU
  3. GPU 进行 前向传播 计算得到 loss
  4. GPU 再通过 反向传播 通过 loss 得到 梯度
  5. GPU 再通过 梯度 更新 参数

伪代码:

model = Model(xx) # 1. 模型初始化
optmizer = Optimizer(xx) # 2. 优化器初始化
output = model(input) # 3. 模型计算
loss = loss_function(output, target) # 4. loss计算
loss.backward() # 5. 反向传播计算梯度
optimizer.step() # 6. 优化器更新参数 

DP

就像 多线程编程 一样,可以通过引入 多个GPU 来提高训练效率,这就引出了最基础的 单机多卡 DP,即 Data Parallel 数据并行。

在这里插入图片描述

  1. CPU 加载数据,并将数据拆分,分给不同的 GPU
  2. GPU0模型 复制到 其他所有 GPU
  3. 每块 GPU 独立的进行 前向传播反向传播 得到 梯度
  4. 其余所有 GPU梯度 传给 GPU0
  5. GPU0 汇总全部 梯度 进行 全局平均 计算
  6. GPU0 通过 全局平均梯度 更新自己的 模型
  7. GPU0 再把最新的 模型 同步到其他 GPU

DPPyTorch伪代码,相比于单机单卡,大部分都没有变化,只是把模型换了DataParallel模型,在PyTorch中通过nn.DataParallel(module, device_ids) 实现:

model = Model(xx) # 1. 模型初始化(没变化)
model_new = torch.nn.DataParallel(model, device_ids=[0,1,2]) # 1.1 启用DP (新增)
optmizer = Optimizer(xx) # 2. 优化器初始化(不变)
output = model_new(input) # 3. 模型计算,替换使用DP(变更)
loss = loss_function(output, target) # 4. loss计算(不变)
loss.backward() # 5. 反向传播计算梯度(不变)
optimizer.step() # 6. 优化器更新参数 (不变)

可见DP使用上非常简单,通过nn.DataParallel套上之前的模型即可。

但是DP存在 2个 比较严重的问题:

  1. 数据传输量较大:不考虑CPU将input数据拆分传输给每块GPU,单独看GPU间的数据传递;对于GPU0它需要把整个模型的参数广播到其他所有GPU,假设有 N N N块GPU,那么就需要传输 ( N − 1 ) ∗ w (N-1)*w (N1)w参数,同时GPU0也需要从其他所有GPU上Reduce所有梯度,那么就要传输 ( N − 1 ) ∗ g (N-1)*g (N1)g,所以对于GPU0来说要传输 ( N − 1 ) ∗ ( w + g ) (N-1)*(w +g) (N1)(w+g)的数据,同理对于其他GPU来说,要传输与来自GPU0的参数,与传出自己那份梯度。所以整体上个说,GPU数量多 N N N越大,传输的数据量就越多。
  2. GPU0的压力太大:它要收集梯度、更新参数、同步参数,计算和通信压力都很大

接下来看一下更高级用法 DDP

DDP

DDPDistributed Data Parallel,多机多卡的分布式数据并行。

在这里插入图片描述
DP 最主要的区别就是,解决了 DP主节点瓶颈,实现了真正的 分布式通信。

而精髓就是 Ring-AllReduce,下面介绍它是如何实现 梯度累计 的:

  1. 假设梯度目前都是单独存在于不同GPU上,而目标是将三个GPU的梯度进行累计,也就是得到下图中三个梯度的和,a0+a1+a2b0+b1+b2c0+c1+c2
    在这里插入图片描述
  2. 首先第一阶段:GPU0a0发送给GPU1去求和a0+a1GPU1b1发送给GPU2去求和b1+b2GPU2c2发送给GPU0去求和c0+c2
    在这里插入图片描述
  3. 然后,继续累加,将GPU0上的c0+c2发送给GPU1去求c0+c1+c2GPU1a0+a1发送给GPU2去求a0+a1+a2,将GPU2b1+b2发送给GPU0去求b0+b1+b2
    在这里插入图片描述
  4. 此时第一阶段完成,通过Scatter-Reduce将参数分发后集合,分别得到了各个参数梯度累计结果
    在这里插入图片描述
  5. 之后的第二节阶段,通过All-Gather将各个参数的梯度进行传播,使得每个GPU上都得到了完整的梯度结果
  6. 首先,GPU0将完整的b0+b1+b2传递给GPU1,同理GPU1GPU2也传递完整的梯度
    在这里插入图片描述
  7. 最后,再将剩余的梯度进行传递
    在这里插入图片描述
  8. 最终每个设备得到了所有参数的完整梯度累计
    在这里插入图片描述

DDPRing-AllReduce 中还有一个细节:如果每个参数都这么Ring着进行信息梯度累计,那么通信压力太大了;

所以设计了,通过将参数分桶聚合,也就是一个桶中维护了多个参数,当整个桶中的所有梯度都计算完毕后,再以桶维度进行Ring梯度累计,这样降低了通信压力,提高了训练效率。

DDP的落地,相较于DP会复杂很多,首先简单理解几个概念:

  • world:代表着DDP集群中的那些卡的
  • rankworld中,每张卡的唯一标识
  • ncclgloo:都是通信库,也就是那些分布式原语的实现,现在普遍都用老黄家的NCCL,搭配RMDA食用效率更高

接下来看一下DDPPyTorch伪代码:

# 首先需要在每张卡,也就是进程单位设置一下,可以理解为在“组网” (新增)
import torch
import torch.distributed as dist
dist.init_process_group(backend = "nccl", # 使用NCCL通信rank = xx, # 这张卡的标识world_size = xx # 所有卡的数量
)
torch.cuda.set_device(rank) # 绑定这个进程的GPU# 然后是模型定义(变化)
model = Model(xx).cuda(rank)
model_ddp = nn.parallel.DistributedDataParallel(mode, device_ids=[rank]) # 相较于DP,这里用DDP来包装模型# 优化器(没变)
optimizer = Optimizer(xx)# 分布式数据加载(新增)
train_sampler = torch.utils.data.distributed.DsitributedSampler(dataset,num_replicas = world_size,rank = rank
)
dataloader = DataLoader(dataset,batch_size = per_gpu_batch_size,sampler = train_sampler
)# 训练(不变)
output = model_ddp(input)
loss = loss_function(output, target)
loss.backward()
optimizer.step()# 训练后结束"组网"
dist.destroy_process_group()# 使用torchrun启动DDP
torchrun train.py # torchrun是pytorch官方DDP的最佳实践,就别用其他的了

FSDP

不论是DP还是DDP数据并行,都有一个核心问题:模型在每个GPU上都存储一份,如果模型特别大,单卡显存不足的话就无法训练。

这就引入了 FSDP(fully sharded data parallel)核心思想是:把模型的参数、梯度、优化器状态 分片存储,显著降低显存占用。

分片机制:

  • 参数分片:把模型的参数切分到所有GPU上,每个GPU仅存储部分参数
  • 前向传播:通过 AllGather 收集完整参数 -> 计算 -> 丢弃 非本地分片(不在显存中存储,仅仅是计算用)
  • 反向传播:通过 AllGather 收集参数 -> 计算梯度 -> 再通过 reduce-scatter同步梯度分片
  • 优化器状态:每个GPU仅维护与其参数分片对应的优化器状态

但这时候就会有疑问了:把模型分片存储,这还算DP吗,这不成了MP么?

确实,FSDP融合DPMP两种思想,但核心仍然是DP,因为它仍然是在 数据维度 进行并行(不同GPU处理不同数据),并且每个GPU都独立的完整前向+反向传播;这是用DP的思想,去解决DP单卡显存瓶颈的问题。

“FSDP is DP with model sharding, not MP. It extends DP beyond single-device memory limits.”
—— PyTorch Distributed Team, Meta AI

下面展示FSDPFULLY_SHARD策略,也就是对标ZeRO-3的训练流程:

  1. 通过FULLY_SHARD策略,将参数、梯度、优化器状态进行了分片
    在这里插入图片描述
  2. 前向传播中,由于每个GPU都只有部分参数,所以当走到缺失那部分参数的时候,依赖其他GPU将参数传进来,执行完毕后就丢弃;通过这种方式,使得即使每个GPU只保存部分参数,但依然可以完成整个前向传播
    在这里插入图片描述
  3. 当得到output开始计算梯度时,每个GPU完整自己那部分的梯度计算,在此过程中如果本地没有相对应的参数,也依然需要从其他GPU传过来;当完成梯度计算后,再把梯度发送给负责更新这部分参数的优化器分片的GPU,由它进行本地参数更新;这样就完成了一次前向+反向传播
    在这里插入图片描述

再来看看FSDPPyTorch伪代码:

# “组网”,也就是设置分布式环境方式和DDP没有区别(不变)
import torch.distributed as dist
from torch.distrbuted.fsdp import FullyShardDataParallel as FSDP
def setup(rank, world_size): dis.init_process_group("nccl", rank=rank, world_size=world_size)torch.cuda.set_device(rank)# 使用FSDP包装模型,同时设置分片策略(新增)
from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy
model = Model(xx)
model_fsdp = FSDP(mode, auto_wrap_policy=size_based_auto_wrap_policy, # 按层大小自动分片mixed_precision=True, # 启用混合精度device_id=rank,sharding_strategy=torch.distributed.ShardingStrategy.FULLY_SHARD # 相当于ZeRO-3
)# 数据加载和分布式采样,和DDP没有区别(不变)
from torch.utils.data.distributed import DistributedSampler
dataset = datasets(xx)
sampler = DistributedSampler(dataset, num_replicas=world_size, rank=rank)
dataloader == torch.utils.data.DataLoader(datase4t, batch_size=64, sampler=sampler)# 训练和DP、DDP没有区别(不变)
for epoch in range(epochs):sampler.set_epoch(epoch)for batch in dataloader:data, target = batck[0].to(rank), batch[1].to(rank) # H2Doptimizer.zero_grad()output = model_fsdp(mode) # 使用fsdp包装的model进行前向传播loss = loss(output, target)loss.backward()optimizer.step()

ZeRO1/2/3

ZeRO 是微软家 DeepSpeed 中的核心技术,思想和 FSDP 是相同,二者都是 通过分片消除模型冗余存储,扩大分布式并行训练能力,只不过 FSDPPyTorch 的官方实现版。

ZeRO(Zero Redundancy Optimizer)有三种策略:

  • ZeRO-1:只分片 优化器状态
  • ZeRO-2:分片 梯度优化器状态 ,对应了 FSDPSHARD_GRAD_OP 策略
  • ZeRO-3:分片 参数梯度优化器状态,对应了 FSDPFULLY_SHARD 策略

虽然 ZeRO 因为深度集成在 DeepSpeed 中,还可以利用上 DeepSpeed 的其他特性,但从生态偏好上讲,个人更推荐使用 PyTorch官方的 FSDP

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

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

相关文章

LeeCode144二叉树的前序遍历

项目场景: 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 示例 1: 输入:root [1,null,2,3] 输出:[1,2,3] 解释: 示例 2: 输入:root [1,2,3,4,5,null,8,null,null,6,7…

日本生活:日语语言学校-日语作文-沟通无国界(3)-题目:わたしの友達

日本生活:日语语言学校-日语作文-沟通无国界(3)-题目:わたしの友達 1-前言2-作文原稿3-作文日语和译本(1)日文原文(2)对应中文(3)对应英文 4-老师…

使用 rsync 拉取文件(从远程服务器同步到本地)

最近在做服务器迁移,文件好几个T。。。。只能单向访问,服务器。怎么办!!! 之前一直是使用rsync 服务器和服务器之间的双向同步、备份(这是推的)。现在服务器要迁移,只能单向访问&am…

Linux 并发编程:从线程池到单例模式的深度实践

文章目录 一、普通线程池:高效线程管理的核心方案1. 线程池概念:为什么需要 "线程工厂"?2. 线程池的实现:从 0 到 1 构建基础框架 二、模式封装:跨语言线程库实现1. C 模板化实现:类型安全的泛型…

2013年SEVC SCI2区,自适应变领域搜索算法Adaptive VNS+多目标设施布局,深度解析+性能实测

目录 1.摘要2.自适应局部搜索原理3.自适应变领域搜索算法Adaptive VNS4.结果展示5.参考文献6.代码获取7.算法辅导应用定制读者交流 1.摘要 VNS是一种探索性的局部搜索方法,其基本思想是在局部搜索过程中系统性地更换邻域。传统局部搜索应用于进化算法每一代的解上&…

详细介绍医学影像显示中窗位和窗宽

在医学影像(如DICOM格式的CT图像)中,**窗宽(Window Width, WW)和窗位(Window Level, WL)**是两个核心参数,用于调整图像的显示对比度和亮度,从而优化不同组织的可视化效果…

Unity_VR_如何用键鼠模拟VR输入

文章目录 [TOC] 一、创建项目1.直接创建VR核心模板(简单)2.创建3D核心模板导入XR包 二、添加XR设备模拟器1.打开包管理器2.添加XR设备模拟器3.将XR设备模拟器拖到场景中4.运行即可用键盘模拟VR输入 一、创建项目 1.直接创建VR核心模板(简单&…

SpringBoot定时监控数据库状态

1.application.properties配置文件 # config for mysql spring.datasource.url jdbc\:mysql\://127.0.0.1\:3306/数据库名?characterEncoding\utf8&useSSL\false spring.datasource.username 账号 spring.datasource.password 密码 spring.datasource.validation-quer…

Qt联合Halcon开发一:Qt配置Halcon环境【详细图解流程】

在Qt中使用Halcon库进行图像处理开发,可以有效地结合Qt的图形界面和Halcon强大的计算机视觉功能。下面是详细的配置过程,帮助你在Qt项目中成功集成Halcon库。 步骤 1: 安装Halcon软件并授权 首先,确保你已经在电脑上安装了Halcon软件&#x…

一体化(HIS系统)医院信息系统,让医疗数据互联互通

在医疗信息化浪潮下,HIS系统、LIS系统、PACS系统、电子病历系统等信息系统成为医疗机构必不可少的一部分,从患者挂号到看诊,从各种检查到用药,从院内治疗到院外管理……医疗机构不同部门、不同科室的各类医疗、管理业务几乎都初步…

Spring Boot 的 3 种二级缓存落地方式

在高并发系统设计中,缓存是提升性能的关键策略之一。随着业务的发展,单一的缓存方案往往无法同时兼顾性能、可靠性和一致性等多方面需求。 此时,二级缓存架构应运而生,本文将介绍在Spring Boot中实现二级缓存的三种方案。 一、二…

Android Studio Profiler使用

一:memory 参考文献: AndroidStudio之内层泄漏工具Profiler使用指南_android studio profiler-CSDN博客

Zephyr boot

<!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Zephyr设备初始化机制交互式解析…

腾讯地图Web版解决热力图被轮廓覆盖的问题

前言 你好&#xff0c;我是喵喵侠。 还记得那天傍晚&#xff0c;我正对着电脑调试一个腾讯地图的热力图页面。项目是一个区域人流密度可视化模块&#xff0c;我加了一个淡蓝色的轮廓图层用于表示区域范围&#xff0c;热力图放在下面用于展示人流热度。效果一预览&#xff0c;…

【JVMGC垃圾回收场景总结】

文章目录 CMS在并发标记阶段&#xff0c;已经被标记的对象&#xff0c;又被新生代跨带引用&#xff0c;这时JVM会怎么处理?为什么 Minor GC 会发生 STW&#xff1f;有哪些对象是在栈上分配的&#xff1f;对象在 JVM 中的内存结构为什么需要对齐填充&#xff1f;JVM 对象分配空…

3_STM32开发板使用(STM32F103ZET6)

STM32开发板使用(STM32F103ZET6) 一、概述 当前所用开发板为正点原子精英板,MCU: STM32F103ZET6。一般而言,拿到板子之后先要对板子有基础的认识,包括对开发板上电开机、固件下载、调试方法这三个部分有基本的掌握。 二、系统开机 2.1 硬件连接 直接接电源线或Type-c线…

crackme012

crackme012 名称值软件名称attackiko.exe加壳方式无保护方式serial编译语言Delphi v1.0调试环境win10 64位使用工具x32dbg,PEid破解日期2025-06-18 -发现是 16位windows 程序环境还没搭好先留坑

CppCon 2016 学习:I Just Wanted a Random Integer

你想要一个随机整数&#xff0c;用于模拟随机大小的DNA读取片段&#xff08;reads&#xff09;&#xff0c;希望覆盖不同长度范围&#xff0c;也能测试边界情况。 代码部分是&#xff1a; #include <cstdlib> auto r std::rand() % 100;它生成一个0到99之间的随机整数&…

MySQL层级查询实战:无函数实现部门父路径

本次需要击毙的MySQL函数 函数主要用于获取部门的完整层级路径&#xff0c;方便在应用程序或SQL查询中直接调用&#xff0c;快速获得部门的上下级关系信息。执行该函数之后简单使用SQL可以实现数据库中部门名称查询。例如下面sql select name,GetDepartmentParentNames(du.de…

Python初学者教程:如何从文本中提取IP地址

Python初学者教程:如何从文本中提取IP地址 在网络安全和数据分析领域,经常需要从文本文件中提取IP地址。本文将引导您使用Python创建一个简单但实用的工具,用于从文本文件提取所有IP地址并将其保存到新文件中。即使您是编程新手,也可以跟随本教程学习Python的基础知识! …