# 单机单卡 代码示例
# 1 判断cuda是否可用
if torch.cuda.is_available():
# 2 设置卡 只对"0"卡可见
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
else:
return
# 3 模型拷贝 原地操作
model.cuda()
# 4 数据拷贝 非原地操作 返回新设备的对象
# 不是cuda的方法 其实是tensor的方法
data = data.cuda()
# 加载
# cpu
ckpt = torch.load(resume)
# cuda
ckpt = torch.load(resume,map_location=torch.device("cpu"))
ckpt = torch.load(resume,map_location=torch.device("cuda:0"))
缩写:DP;
DP已经淘汰,现在大部分用DDP(见2.2);
DP缺点:
注意:
BATCH_SIZE = 64 * 4
# 单机多卡
# 1判断cuda是否可用
if torch.cuda.is_available():
# 2判断是否有多卡
if torch.cuda.device_count() > 1:
print()
else:
return
else:
return
# 3设置卡 对多卡可见
nn.DataParallel(model.cuda(), device_ids = [0,1,2,3])
# 4数据拷贝 非原地操作 返回新设备的对象
# 不是cuda的方法 其实是tensor的方法
data = data.cuda()
# 保存
# cpu
model.state_dict()
# 多卡
model.module.state_dict()
# 加载
# cpu gpu
ckpt = torch.load(resume,map_location=torch.device("cuda:0"))
model.load_state_dict()
缩写:DDP;
多进程
NCCL通信方式(见5);
world_size:几张卡;
rank:节点(哪个机子 单卡默认为0);
注意:
# 从命令行接受参数
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", help="local device id on curr node",type=int)
args = parser.parse_args()
if torch.cuda.is_available():
# 2判断是否有多卡
if torch.cuda.device_count() > 1:
print()
else:
return
else:
return
# 初始化一个进程组
n_gpus = 2
torch.distributed.init_process_group("nccl",world_size=n_gpus,rank=args.local_rank)
# 指定卡
torch.cuda.set_device(args.local_rank)
# 模型拷贝 放入DDP
model = nn.parallel.DistributedDataParallel(model.cuda(local_rank),device_ids = local_rank)
# 创建train_sampler
train_sampler = DistributedSampler(train_dataset)
train_data_loader = torch.util.data.DataLoader(
train_dataset,
batch_size=BATCH_SIZE,
collate_fn=collate_fn,
sampler=train_sampler
)
# 每epoch修改
# 为了让每张卡在每个周期中得到的数据是随机的
train_sampler.set_epoch(epoch_index)
# 数据拷贝
data = data.cuda(local_rank)
# 保存 local_rank=0上保存
# 加载
# 运行
python -m torch.distributed.launch --nproc_per_node=n_gpus train.py
torch.nn.parallel.DistributedDataParallel
node_rank:区分是哪个机子;
python -m torch.distributed.launch
--nproc_per_node=n_gpus
--nnodes=2
--node_rank=0
--master-addr="主节点IP"
--master_port=主节点端口 train.py
python -m torch.distributed.launch
--nproc_per_node=n_gpus
--nnodes=2
--node_rank=1
--master-addr="主节点IP"
--master_port=主节点端口 train.py
当模型的参数太大,单个GPU无法容纳,需要将模型的不同层拆分到不同GPU上;
官网文档:https://pytorch.org/tutorials/intermediate/model_parallel_tutorial.html;
举个例子:
import torch
import torch.nn as nn
import torch.optim as optim
class ToyModel(nn.Module):
def __init__(self):
# net1和net2放入不同GPU训练
super(ToyModel, self).__init__()
self.net1 = torch.nn.Linear(10, 10).to('cuda:0')
self.relu = torch.nn.ReLU()
self.net2 = torch.nn.Linear(10, 5).to('cuda:1')
def forward(self, x):
x = self.relu(self.net1(x.to('cuda:0')))
return self.net2(x.to('cuda:1'))
分布式训练:在多个机器上一起训练模型,提高训练效率。
Parameter Server架构包含一到多个server节点,多个worker节点;
server节点保存模型参数,如果有多个server节点会把模型参数保存多份到多个server上;
特点:随着worker数量增加,模型的运行效率并不是线性提升的(worker的增加导致worker与server通信时长增加);
worker负责使用server上的参数以及本worker上的数据计算梯度;
训练过程:
实现方式:
详情见:Ring Allreduce - 简书 (jianshu.com);
特点:运行效率随着worker数量的增加线性增加;
Ring AllReduce架构中没有server,都是worker,所有worker组成一个环形,每个worker和另外两个worker相连。每个worker都只和相邻的两个worker进行信息传递。每个worker上都有一份完整的模型参数,并进行梯度计算和更新。
训练过程(假设5个worker):
NCCL全称NVIDIA Collective Communication Library;
NCCL的聚合通信接口采用异步调用的方式(通过stream来实现),接口调用后立即返回,用户需要调用cudaStreamSynchronize等待聚合通信完成。
NCCL聚合通信包括以下接口: