[分布式训练] 单机多卡的正确打开方式:理论基础

本文介绍了分布式训练的三种主要方式:模型并行和数据并行,以及同步更新和异步更新。在模型过大无法容纳于单个GPU时,可以选择模型并行,但会增加通信开销;数据并行则更为常见,适合模型可以放入单个GPU的情况。同步更新确保所有GPU在更新参数时保持一致,而异步更新可能导致梯度过时和loss抖动。此外,文章还对比了ParameterServer和RingAllReduce两种参数同步算法,RingAllReduce通过减少通信成本提高了并行效率。最后,文中提到RingAllReduce已被各大框架采纳,成为提高深度学习模型训练效率的有效手段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[分布式训练] 单机多卡的正确打开方式:理论基础

转自:https://fyubang.com/2019/07/08/distributed-training/

瓦砾由于最近bert-large用的比较多,踩了很多分布式训练的坑,加上在TensorFlow和PyTorch之间更换,算是熟悉了一下各类框架的分布式训练接口,由于集中在一起讲可能比较乱,笔者准备分三到四篇来讲一下深度学习的分布式训练。这一篇先讲一下“分布式训练的类型与算法”。

分布式训练的需求和重要性不需要多说,随着GPT、BERT、xlnet这些预训练模型的出现,普通的16G的显存已经不足以支撑深度学习模型训练的要求了,这时候就需要用到分布式训练来提高效率。

注意:这个系列主要介绍单机多卡的分布式训练情况(这种情况比较常见,土豪和大佬们请忽略)。

总的来说,分布式训练分为这几类:

  • 按照并行方式来分:模型并行 vs 数据并行
  • 按照更新方式来分:同步更新 vs 异步更新
  • 按照算法来分:Parameter Server算法 vs AllReduce算法

模型并行 vs. 数据并行

假设我们有n张GPU:

  • 模型并行:不同的GPU输入相同的数据,运行模型的不同部分,比如多层网络的不同层。
  • 数据并行:不同的GPU输入不同的数据,运行相同的完整模型。

在这里插入图片描述

当模型非常大,一张GPU已经存不下的时候,可以使用模型并行,把模型的不同部分交给不同的机器负责,但是这样会带来很大的通信开销,而且模型并行各个部分存在一定的依赖,规模伸缩性差。因此,通常一张可以放下一个模型的时候,会采用数据并行的方式,各部分独立,伸缩性好。

同步更新 vs. 异步更新

对于数据并行来说,由于每个GPU负责一部分数据,那就涉及到如果更新参数的问题,分为同步更新和异步更新两种方式。

  • 同步更新:每个batch所有GPU计算完成后,再统一计算新权值,然后所有GPU同步新值后,再进行下一轮计算。
  • 异步更新:每个GPU计算完梯度后,无需等待其他更新,立即更新整体权值并同步。

在这里插入图片描述

在这里插入图片描述

同步更新有等待,速度取决于最慢的那个GPU;异步更新没有等待,但是涉及到更复杂的梯度过时,loss下降抖动大的问题。所以实践中,一般使用同步更新的方式。

Parameter Server算法 vs. Ring AllReduce算法

这里讲一下常用的两种参数同步的算法:PS 和 Ring AllReduce。

假设有5张GPU:

  • Parameter Server:GPU 0将数据分成五份分到各个卡上,每张卡负责自己的那一份mini-batch的训练,得到grad后,返回给GPU 0上做累积,得到更新的权重参数后,再分发给各个卡。
  • Ring AllReduce:5张以环形相连,每张卡都有左手卡和右手卡,一个负责接收,一个负责发送,循环4次完成梯度累积,再循环4次做参数同步。分为Scatter Reduce和All Gather两个环节。

Parameter Server算法

在这里插入图片描述

Parameter Server的思想其实有点类似于MapReduce,以上讲同步异步的时候,都是用的这种算法,但是它存在两个缺点:

  1. 每一轮的训练迭代都需要所有卡都将数据同步完做一次Reduce才算结束,并行的卡很多的时候,木桶效应就会很严重,计算效率低。
  2. 所有的GPU卡需要和Reducer进行数据、梯度和参数的通信,当模型较大或者数据较大的时候,通信开销很大。

假设有 N N N个GPU,通信一次完整的参数所需时间为 K K K,那么使用PS架构,花费的通信成本为:
T = 2 ( N − 1 ) K T=2(N-1)K T=2(N1)K
所以我们亟需一种新的算法来提高深度学习模型训练的并行效率。

Ring AllReduce算法

2017 年 Facebook 发布了《Accurate, large minibatch SGD: Training ImageNet in 1 hour 》验证了大数据并行的高效性,同年百度发表了《Bringing HPC techniques to deep learning 》,验证了全新的梯度同步和权值更新算法的可行性,并提出了一种利用带宽优化环解决通信问题的方法——Ring AllReduce。

在这里插入图片描述

Parameter Service最大的问题就是通信成本和GPU的数量线性相关。而Ring AllReduce的通信成本与GPU数量无关。Ring AllReduce分为两个步骤:Scatter Reduce和All Gather。

Scatter Reduce过程:首先,我们将参数分为N份,相邻的GPU传递不同的参数,在传递N-1次之后,可以得到每一份参数的累积(在不同的GPU上)。

在这里插入图片描述

All Gather:得到每一份参数的累积之后,再做一次传递,同步到所有的GPU上。

在这里插入图片描述

根据这两个过程,我们可以计算到All Reduce的通信成本为:
T = 2 ( N − 1 ) K N T=2(N-1)\frac{K}{N} T=2(N1)NK
可以看到通信成本T与GPU数量无关。

由于All Reduce算法在通信成本上的优势,现在几个框架基本上都实现了其对于的官方API,后面几篇,瓦砾会跟大家一起过一遍TF,Torch的分布式训练API具体是怎么用的,有哪些坑。

Reference

  1. 是时候放弃Tensorflow,拥抱Horovod了
  2. Tensorflow单机多卡实现
  3. Binging HPC Techniques to Deep Learning
  4. Training Neural Nets on Larger Batches: Practical Tips for 1-GPU, Multi-GPU & Distributed setups
<think>好的,用户想解决NGPU利用率低和训练速度慢的问题。首先,我需要回忆一下常见的优化方法。根据用户提供的引用[1],里面有提到使用分布式数据并行(DDP)进行GPU训练,这可能是一个方向。不过用户现在可能是在单上遇到问题,或者配置不当。 首先,检查GPU是否被正确识别和使用。用户可能没有安装合适的驱动或者CUDA,导致PyTorch/TensorFlow无法调用GPU。可以建议他们运行nvidia-smi查看GPU状态,或者用代码检查torch.cuda.is_available()。 然后,数据加载可能成为瓶颈。如果数据预处理太慢,或者DataLoader的num_workers设置不当,会导致GPU等待数据,利用率下降。建议增加num_workers,或者使用pin_memory和prefetch来加速数据加载。另外,可能数据增强太复杂,可以尝试简化或使用更高效的方法。 接下来,批处理大小(batch size)是否合适。太小的话,GPU无法充分利用;太大可能导致内存不足。需要找到一个平衡点,或者使用梯度累积(gradient accumulation)来模拟更大的batch size。引用[1]里提到的梯度累积方法可能有用,但要注意是否影响了训练速度。 模型本身的效率也很重要。比如,是否存在不必要的计算图保存(比如在不需要梯度的地方用了requires_grad),或者频繁使用小的运算导致kernel启动开销大。建议使用混合精度训练(AMP),减少内存占用并加速计算。同时,检查是否有CPU和GPU之间的频繁数据传输,比如在循环中不断移动张量,应该尽量在GPU上完成操作。 另外,混合精度训练可以结合NVIDIA的Apex库或者PyTorch自带的AMP,这样能减少显存使用,提升计算速度。同时,启用cudNN的自动调优功能,让框架选择最优的算法。 软件版本也是一个因素,比如PyTorch的版本是否支持最新的CUDA版本,或者是否有已知的性能问题。更新驱动和框架版本可能解决一些问题。 对于分布式训练,如果用户确实在使用GPU,需要确保正确配置了DDP,每个进程的负载均衡,避免通信开销过大。但用户的问题可能更集中在单情况,所以可能先排除其他因素。 最后,使用性能分析工具,比如PyTorch的autograd.profiler或Nsight Systems,来定位具体是哪些操作耗时,从而有针对性地优化。比如,找出是前向传播、反向传播还是数据加载部分最慢,然后针对性地优化。 总结下来,可能的解决步骤是:检查GPU识别和配置→优化数据加载→调整batch size和梯度累积→启用混合精度→减少不必要的计算和传输→使用性能分析工具定位瓶颈。同时,参考用户提到的分布式训练和梯度累积方法,可能需要结合这些技术。</think>针对NGPU利用率低及训练速度慢的问题,以下从底层优化到框架配置提供分步解决方案: ### 一、基础检查与配置优化 1. **验证GPU识别状态** 通过`nvidia-smi`命令确认GPU是否被系统识别,检查驱动版本是否符合CUDA要求。在代码中执行`torch.cuda.is_available()`验证框架是否检测到GPU[^1]。 2. **批处理尺寸(Batch Size)调优** 逐步增加`batch_size`直至达到显存上限(通过`nvidia-smi`监控显存占用),若显存不足可采用梯度累积: ```python optimizer.zero_grad() for i, (inputs, labels) in enumerate(data_loader): outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() # 梯度累积 if (i+1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad() ``` ### 二、计算图与数据流优化 3. **禁用非必要梯度计算** 在推理或冻结层时使用`torch.no_grad()`上下文管理器: ```python with torch.no_grad(): val_outputs = model(val_inputs) ``` 4. **数据加载加速方案** 启用进程预加载与内存锁定: ```python DataLoader(dataset, num_workers=4, pin_memory=True, persistent_workers=True, prefetch_factor=2) ``` *注:`num_workers`建议设置为CPU物理核心数×0.75* ### 三、混合精度与内核优化 5. **自动混合精度(AMP)配置** 通过NVIDIA Apex或PyTorch原生AMP实现: ```python scaler = torch.cuda.amp.GradScaler() with torch.amp.autocast(device_type='cuda'): outputs = model(inputs) loss = criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() ``` 6. **cudNN算法选择策略** 启用自动算法选择优化器: ```python torch.backends.cudnn.benchmark = True # 自动选择最优卷积算法 torch.backends.cudnn.deterministic = False # 允许算法随机性 ``` ### 四、分布式训练配置 7. **GPU并行方案选择** 单机推荐使用`DistributedDataParallel`(DDP): ```python torch.distributed.init_process_group(backend='nccl') model = DDP(model, device_ids=[local_rank]) ``` ### 五、性能分析工具使用 8. **PyTorch性能分析器** 定位计算瓶颈: ```python with torch.autograd.profiler.profile(use_cuda=True) as prof: training_step() print(prof.key_averages().table(sort_by="cuda_time_total")) ``` ### 典型优化效果对比表 | 优化措施 | 理论加速比 | 显存节省量 | |------------------|------------|------------| | AMP混合精度 | 1.5-3× | 30-50% | | DDP训练 | 近似线性 | 分布式分摊 | | 梯度累积(step=4) | - | 75% |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值