单机多卡训练-DDP

DDP原理:

单机多卡训练使用分布式数据并行(Distributed Data Parallel,DDP)的原理是将模型参数分布到多个 GPU 上,每个 GPU 计算部分数据的梯度,然后通过梯度求和的方式进行参数更新。这样可以加速训练过程,同时有效地利用多个 GPU 的计算资源。

步骤:

  1. 模型复制: 将模型复制到每个 GPU 上。每个 GPU 上的模型副本都包含相同的参数,初始权重相同。

  2. 数据划分: 训练数据被划分成多个小批次。每个 GPU 负责处理其中的一部分数据。

  3. 前向传播: 每个 GPU 上的模型副本独立进行前向传播。即,每个 GPU 将其分配到的数据通过模型前向传播,得到预测结果。

  4. 梯度计算: 根据每个 GPU 上的预测结果和对应的真实标签,计算损失函数。然后,每个 GPU 计算自己分配的数据对模型参数的梯度。

  5. 梯度汇总: 利用分布式数据并行的特性,将每个 GPU 上的梯度汇总到主 GPU 或 CPU 上。通常使用 all-reduce 操作,即将所有梯度相加,然后平均。

  6. 梯度更新: 使用在梯度汇总过程中得到的梯度更新模型的参数。更新后的参数被广播到所有 GPU 上。

  7. 重复步骤 3-6: 重复进行前向传播、梯度计算、梯度汇总和参数更新,直到训练过程结束。

数据划分的理解:

在数据并行的训练过程中,数据会被划分成多个小批次,每个 GPU 负责处理其中的一部分数据。这个小批次的大小通常是整个批次大小(batch size)除以 GPU 的数量,以确保每个 GPU 处理到的数据量相对均匀。

举例来说,假设你有一个批次大小为 256 的训练数据,并且有两个 GPU。那么,每个 GPU 将会处理大小为 256 / 2 = 128 的小批次数据。这样,每个 GPU 独立进行前向传播、梯度计算和梯度更新,最后通过梯度汇总保持模型的同步。

为什么快?

DDP通过Ring-Reduce(梯度合并)的数据交换方法提高了通讯效率,并通过启动多个进程的方式减轻Python GIL的限制,从而提高训练速度。

神经网络中的并行有以下三种形式:

  1. Data Parallelism
    1. 这是最常见的形式,通俗来讲,就是增大batch size提高并行度
      1. 平时我们看到的多卡并行就属于这种。比如DP、DDP都是。这能让我们方便地利用多卡计算资源。
    2. 能加速。
  2. Model Parallelism
    1. 把模型放在不同GPU上,计算是并行的。
    2. 有可能是加速的,看通讯效率。
  3. Workload Partitioning
    1. 把模型放在不同GPU上,但计算是串行的。
    2. 不能加速。

注意点:

1. 保存模型

考虑到以后可能需要单卡加载你多卡训练的模型,建议在保存模型时,去除模型参数字典里面的module,如何去除呢,使用model.module.state_dict()代替model.state_dict()

2.  每一个epoch里面真正的打乱数据

for epoch in range(args.num_epochs):
    train_sampler.set_epoch(epoch)  # shuffle数据

3. 设置多个gpu的BN同步(下面第一句代码)

# 设置模型并行
model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model)  # 多个gpu的BN同步
model = torch.nn.parallel.DistributedDataParallel(model.cuda())

SyncBatchNorm 是一个 PyTorch 模块,可对小批量数据执行批量标准化,但会同步分布式训练环境中所有 GPU 的平均值和标准差统计数据。这与标准 PyTorch BatchNorm 模块形成对比,后者在每个 GPU 上独立计算平均值和标准差统计数据

换种方式说:

SyncBN的原理很简单:SyncBN利用分布式通讯接口在各卡间进行通讯,从而能利用所有数据进行BN计算。为了尽可能地减少跨卡传输量,SyncBN做了一个关键的优化,即只传输各自进程的各自的 小batch mean和 小batch variance,而不是所有数据。

4. 固定随机种子

单卡时:

import random
import numpy as np
import torch

def init_seeds(seed=0, cuda_deterministic=True):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    # Speed-reproducibility tradeoff https://pytorch.org/docs/stable/notes/randomness.html
    if cuda_deterministic:  # slower, more reproducible
        cudnn.deterministic = True
        cudnn.benchmark = False
    else:  # faster, less reproducible
        cudnn.deterministic = False
        cudnn.benchmark = True
        

def main():
    # 一般都直接用0作为固定的随机数种子。
    init_seeds(0)

单机多卡时,需要给不同的进程分配不同的、固定的随机数种子:

def main():
    rank = torch.distributed.get_rank()
    # 问题完美解决!
    init_seeds(1 + rank)

注意自己在写代码时只写了torch.manual_seed(seed),这不够全面,最好按照以上init_seed函数,使用 random.seed、np.random.seed、torch.manual_seed 分别初始化 Python 内置的 random 模块、NumPy 库、以及 PyTorch 库的随机数生成器。根据 cuda_deterministic 的设置,可能会设置 cudnn.deterministic 为 True,从而使得使用 CUDA 的部分更加确定性。这通常会导致训练速度稍微变慢,但提高了可重现性。

4. dist.barrier() 

 dist.barrier()

别的博主解释: 这一句作用是:所有进程(gpu)上的代码都执行到这,才会执行该句下面的代码。

这里美誉懂,不知道该不该加或者怎么加,xdm也可以解释下。

写的很完整的一篇文章:

[原创][深度][PyTorch] DDP系列第三篇:实战与技巧 - 知乎 (zhihu.com)

[原创][深度][PyTorch] DDP系列第一篇:入门教程 - 知乎 (zhihu.com)

[原创][深度][PyTorch] DDP系列第三篇:实战与技巧 - 知乎 (zhihu.com)

 

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值