pytorch 单机多卡--DistributedDataParallel+混合精度--提高速度,减少内存占用

本文探讨了DataParallel在内存平衡上的局限性,并揭示了DistributedDataParallel的高效之处。通过实例展示如何配置混合精度训练和使用DistributedDataParallel以解决内存问题,以及在使用过程中遇到的常见问题和解决方案,如未使用的参数检测和端口冲突。
部署运行你感兴趣的模型镜像

DataParallel巨慢,DistributedDataParallel巨快!
在这里插入图片描述
DataParallel的并行处理机制:DataParallel是将输入一个 batch 的数据均分成多份,分别送到对应的 GPU 进行计算,各 个 GPU 得到的梯度累加。与 Module 相关的所有数据也都会以浅复制的方式复制多份。每个 GPU 将针对各自的输入数据独立进行 forward 计算,在 backward 时,每个卡上的梯度会汇总到原始的 module 上,再用反向传播更新单个 GPU 上的模型参数,再将更新后的模型参数复制到剩余指定的 GPU 中,以此来实现并行。

DataParallel会将定义的网络模型参数默认放在GPU 0上,所以dataparallel实质是可以看做把训练参数从GPU拷贝到其他的GPU同时训练,这样会导致内存和GPU使用率出现很严重的负载不均衡现象,即GPU 0的使用内存和使用率会大大超出其他显卡的使用内存,因为在这里GPU0作为master来进行梯度的汇总和模型的更新,再将计算任务下发给其他GPU,所以他的内存和使用率会比其他的高。
参考链接:
Pytorch 分布式并行训练 + 半精度/混合精度训练

示例:

from torch.utils.data.distributed import DistributedSampler
from apex import amp  # 使用混合精度
import torch.distributed as dist
import argparse, os
import MMVAE#my function file
#如果还import了其他子函数文件,无需对子函数文件中再进行这些操作
# from gpu_mem_track import MemTracker  # 引用显存跟踪代码
os.environ['CUDA_LAUNCH_BLOCKING'] = '1'
os.environ['CUDA_VISIBLE_DEVICES']="4,5,2,8"
print('torch.cuda.device_count()',torch.cuda.device_count())
os.environ["WORLD_SIZE"]='4'   #4个进程
os.environ['MASTER_ADDR']='127.0.0.1'
os.environ['MASTER_PORT']='29500'#此端口不可变,否则会卡住
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", type=int, default=0)
args = parser.parse_args()

dist.init_process_group("nccl", init_method='env://')
rank = dist.get_rank()
local_rank = os.environ['LOCAL_RANK']
master_addr = os.environ['MASTER_ADDR']
master_port = os.environ['MASTER_PORT']
print(f"rank = {rank} is initialized in {master_addr}:{master_port}; local_rank = {local_rank}")
torch.cuda.set_device(rank)
local_rank = rank
torch.cuda.set_device(local_rank)
device = torch.device("cuda", local_rank)
world_size = dist.get_world_size()  # os.environ["WORLD_SIZE"]
print('world_size',world_size)

#数据并行
milan_datase = ……
sampler = DistributedSampler(milan_dataset, num_replicas=world_size, shuffle=True)
    train_loader = DataLoader(dataset=milan_dataset,
                             batch_size=batch_size,
                             shuffle=False,
                             sampler=sampler)
#模型并行
model=……
model.to(device)
optimizer = torch.optim.Adam([{'params': model.parameters()}], lr=0.1)#
model = torch.nn.parallel.DistributedDataParallel(model,
                                                  device_ids=[local_rank],
                                                  output_device=local_rank)

#训练阶段整合loss,测试阶段类似。仅仅是为了打印的时候,打印四个卡的loss的平均。反向传播的时候还是用各自的loss传播。

if batch_i % 10 == 0:
	# We manually reduce and average the metrics across processes. In-place reduce tensor.
	reduced_loss = reduce_tensor(losses.data)
	losses1.update(to_python_float(reduced_loss), all_input.size(1))  # update needs average and number
	torch.cuda.synchronize()
	epoch_loss += losses1.item()

运行:

 #单机4个GPU
python3 -m torch.distributed.launch --nproc_per_node=4 trainModel_distr_gpus.py  

出现错误:

RuntimeError: Expected to have finished reduction in the prior iteration before starting a new one. This error indicates that your module has parameters that were not used in producing loss. You can enable unused parameter detection by (1) passing the keyword argument find_unused_parameters=True to torch.nn.parallel.DistributedDataParallel; (2) making sure all forward function outputs participate in calculating loss. If you already have done the above two steps, then the distributed data parallel module wasn't able to locate the output tensors in the return value of your module's forward function. Please include the loss function and the structure of the return value of forward of your module when reporting this issue (e.g. list, dict, iterable)

使用单卡调试,在loss.backward()之后optimizer.step()之前加入下面代码:

for name, param in model.named_parameters():
    if param.grad is None:
        print(name)

打印出来的参数就是没有参与loss运算的部分,他们梯度为None
在这里插入图片描述
是因为encoder模型中定义了一个没有用到的initial模型,注释掉就好了!在这里插入图片描述
使用混合精度:

from apex import amp  # 使用混合精度
model, optimizer = amp.initialize(model, optimizer,
                                 opt_level="O1")  # 如果使用apex混合精度训练,初始化放在model和optimizer定义之后,DDP之前
with amp.scale_loss(loss, optimizer) as scaled_loss:
   scaled_loss.backward()
optimizer.step()

在这里插入图片描述
在这里插入图片描述
仔细看报错追溯信息,可以看到大概还是混合精度包apex未装好,
在这里插入图片描述
混合精度链接
因为安装amp包没成功,直接使用torch 原生支持的amp。
只需要安装有pytorch就能使用,而且代码也简单。限制条件只有一个就是pytorch的版本一定>1.6。主要是利用了这两个API——torch.cuda.amp.GradScalar 和 torch.cuda.amp.autocast。

from torch.cuda.amp import autocast as autocast, GradScaler
 
>>>>>>>>
other code
>>>>>>>>
 
# 在训练最开始之前实例化一个GradScaler对象
scaler = GradScaler()
 
>>>>>>>>
#alternatively   #注意自定义的model中也要with autocast()
MyModel(nn.Module):
    def forward(self, input):
        with autocast():
            ...
data=*****
model = torch.nn.parallel.DistributedDataParallel(model,
                                                  device_ids=[local_rank],
                                                  output_device=local_rank)
optimizer=optim.SGD(model.parameters(),lr=0.1)
for epoch in epochs:
  for input,target in data:
    optimizer.zero_grad()
>>>>>>>>
        # 前向过程(model + loss)开启 autocast
        with autocast():
            output = model(input)
            loss = loss_fn(output, target)
 
        # Scales loss,这是因为半精度的数值范围有限,因此需要用它放大
        scaler.scale(loss).backward()
 
        # scaler.step() unscale之前放大后的梯度,但是scale太多可能出现inf或NaN
        # 故其会判断是否出现了inf/NaN
        # 如果梯度的值不是 infs 或者 NaNs, 那么调用optimizer.step()来更新权重,
        # 如果检测到出现了inf或者NaN,就跳过这次梯度更新,同时动态调整scaler的大小
        scaler.step(optimizer)
 
        # 查看是否要更新scaler,这个要注意不能丢
        scaler.update()
 
 
>>>>>>>>
other code
>>>>>>>>

更加详细的混合精度教程

命令行改进
自动化启动多进程

单击多卡执行多个并行程序
注意:端口号和py文件名不能重复!
Pytorch distributed RuntimeError: Address already in use

CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2  --master_port 29501  main.py

脚本顺序启动多个程序+传参交互
参考:
传参交互
import os
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(‘-set_num’, help=‘dataset’, type=int, default=2)
parser.add_argument(‘-seq_length’, help=‘history_seq_length’, type=int, default=40)
parser.add_argument(‘-future_length’, help=‘future_seq__length’, type=int, default=1)
args = parser.parse_args()

set_num = args.set_num

脚本编写:run.sh

milan=1
london=2
#history=(40 60 80)
#future=(1 4 8)
history=(60 80 40)
future=(1 4 8)
for((i=0;i<=2;i++))
do
  for((j=0;j<=2;j++))
  do
    python3 -m torch.distributed.launch --nproc_per_node=4 --master_port 29999 trainModel_distr_gpus.py \
                -set_num $london\
                -seq_length ${history[i]} \
                -future_length ${future[j]}
    wait
  done
done

wait
#history1=(60 90 120)
#future1=(1 6 12)
history1=(60 120 90)
future1=(1 6 12)
for((i=0;i<=2;i++))
do
  for((j=0;j<=2;j++))
  do
    python3 -m torch.distributed.launch --nproc_per_node=4 --master_port 29999 trainModel_distr_gpus.py \
                -set_num $milan\
                -seq_length ${history1[i]} \
                -future_length ${future1[j]}
    wait
  done
done

运行:

bash ./run.sh

./run.sh: line 5: syntax error near unexpected token `$‘\r’’
解决

sed -i 's/\r//g' run.sh

挂在后台运行:

nohup bash ./run.sh &

一些坑:
1、如果运行程序时,卡在以下
在这里插入图片描述
大概率是因为运行命令中写的端口号或py文件名与源代码不一致,仔细检查一下!

您可能感兴趣的与本文相关的镜像

PyTorch 2.5

PyTorch 2.5

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

<think>我们正在回答用户的问题:“PythonPyTorch使用CUDA进行模型训练的方法有哪些,是否只能通过Python实现”。首先,我们需要总结PyTorch使用CUDA训练的方法,然后讨论是否只能使用Python。根据引用[1]和[3],我们知道在PyTorch中使用CUDA训练时,需要注意将CUDA张量转换为CPU张量才能进行numpy操作,并且需要检查CUDA环境。同时,引用[4]供了安装PyTorch与CUDA支持的命令。下面我们组织回答:一、PyTorch使用CUDA训练的方法:1.设备设置:将模型和数据移动到GPU上。2.分布式训练:使用DataParallel或DistributedDataParallel进行GPU训练。3.混合精度训练:使用torch.cuda.amp进行自动混合精度训练,减少显存占用,加快训练速度。4.监控GPU使用情况:通过torch.cuda.mem_get_info等监控显存。5.避免CUDA张量与numpy的转换错误:如引用[1]所述,需要将CUDA张量先移到CPU再转numpy。二、是否只能通过Python实现?虽然PyTorch主要支持Python,但也有其他语言的接口,如C++(通过LibTorch)。然而,使用CUDA训练模型的主要和最常见方式是通过Python,因为Python有最完整的库支持。其他语言可能功能受限或不够成熟。因此,回答分为两部分:第一部分:PyTorch使用CUDA训练的方法(详细步骤)第二部分:讨论是否只能通过Python(包括替代方案)注意:引用用户供的引用[1]到[4]来支持我们的回答。我们还需要生成相关问题。</think>### PyTorch 使用 CUDA 进行模型训练的完整指南 #### 一、PyTorch CUDA 训练核心方法 1. **设备设置与数据迁移** 将模型和张量显式移动到 GPU 设备: ```python device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = YourModel().to(device) # 模型迁移 inputs = inputs.to(device) # 数据迁移 labels = labels.to(device) ``` 2. ** GPU 并行训练** 使用 `DataParallel` 或 `DistributedDataParallel`: ```python # 单机方案(引用[3]) if torch.cuda.device_count() > 1: print(f"使用 {torch.cuda.device_count()} 个GPU") model = nn.DataParallel(model) ``` 3. **混合精度训练** 通过 AMP 减少显存占用并加速训练: ```python from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() with autocast(): outputs = model(inputs) loss = criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() ``` 4. **CUDA 张量处理规范** 需遵循 CUDA 与 CPU 数据转换规则(引用[1]): ```python # 错误方式:cuda_tensor.numpy() # 正确方式: numpy_array = cuda_tensor.cpu().detach().numpy() # 添加引用[1] ``` #### 二、训练过程优化技术 | 技术 | 实现方法 | 优势 | |---------------------|--------------------------------------------------------------------------|--------------------------| | **梯度累积** | `accum_steps=4` 后执行 `optimizer.step()` | 模拟更大 batch size | | **显存优化** | 使用 `torch.cuda.empty_cache()` 及时释放缓存 | 避免 OOM(内存溢出) | | **训练监控** | `nvidia-smi` 或 `torch.cuda.memory_allocated()` | 实时监控显存使用[^3] | | **分布式训练** | `torch.distributed.launch` + `DistributedDataParallel` | 支持机训练 | #### 三、是否只能通过 Python 实现? **Python 并非唯一选择**,但存在明显优劣对比: | 语言 | PyTorch 支持方案 | 适用场景 | 限制因素 | |------------|---------------------------------------|---------------------------|------------------------| | **Python** | 原生支持 (`import torch`) | 完整训练/调参/可视化 | 无 | | **C++** | LibTorch (PyTorch C++ API) | 高性能部署 | 缺少高级API | | **Java** | Deeplearning4j (通过 ONNX 转换模型) | 企业系统集成 | 转换性能损失 | | **JavaScript** | TensorFlow.js (模型转换) | 浏览器推理 | 无法直接训练 | > **核心结论**: > 1. **完整训练必须使用 Python**:PyTorch 的自动微分、动态计算图等核心特性仅在 Python 中完整支持 > 2. **推理部署可跨语言**:通过 ONNX/TorchScript 将训练好的模型导出到 C++/Java 等环境(引用[2]) > 3. **CUDA 加速通用性**:无论何种语言最终都依赖 CUDA 底层驱动,Python 有最完善的封装 #### 四、完整训练示例 ```python # 引用[3]环境验证 + 引用[4]安装指南 import torch # 1. 环境检测 print(f"CUDA可用: {torch.cuda.is_available()}") print(f"设备数: {torch.cuda.device_count()}") # 2. 设备配置 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 3. 模型初始化 model = YourModel() model.to(device) # 4. 训练循环 for epoch in range(epochs): for data in train_loader: inputs, labels = data[0].to(device), data[1].to(device) outputs = model(inputs) loss = criterion(outputs, labels) # 混合精度训练 with autocast(): loss.backward() optimizer.step() optimizer.zero_grad() # 5. 模型保存(引用[2]) torch.save(model.state_dict(), 'best.pt') ``` ### 相关问题 1. **如何解决 PyTorch GPU 训练时的负载不均衡问题?** - 分析 `DistributedDataParallel` 与 `DataParallel` 的性能差异及优化策略 2. **ONNX 模型转换后精度下降的根本原因是什么?** - 探讨算子支持差异及量化过程中的精度损失补偿方案 3. **非 Python 环境如何实现 CUDA 内存高效管理?** - 对比 LibTorch (C++) 与 PyTorch (Python) 的显存回收机制差异 4. **混合精度训练中梯度缩放因子的自适应调整策略有哪些?** - 分析动态缩放算法与固定缩放系数的收敛特性差异 > 注:所有 CUDA 操作需遵循设备数据一致性原则,跨设备数据传输需显式调用 `.cpu()` 或 `.cuda()`(引用[1])
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值