import torch
# 判断环境中是否有cuda
print(f"torch.cuda.is_available()={torch.cuda.is_available()}")
# torch.cuda.is_available()=True
# 通用编程语句
device = torch.device('cuda:0' if torch.cuda.is_available()else 'cpu')
print(f"device={device}")
# device=cuda:0
# 代码形式,设置环境变量,第 0 个显卡可见
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
(2) 命令行
# 命令行模式下设置显卡可见,python表示解释器python ;xxxx.py表示的是执行的py文件
CUDA_VISIBLE_DEVICES="0" python xxxx.py
# 直接调用cuda函数,不需要复制(model=model.cuda是错误的),
model.cuda()
# nn.module类中可以查看到函数具体调用细节
# 具体流程如下:
# (1)model.cuda()
# (2)self._apply
# (3)for module in self.children():
module._apply(fn)
数据的拷贝实际上调用的是张量的cuda函数[Tensor = Tensor.cuda()]
,将张量拷贝到GPU上
# 对于 X 来说
source = x = token_index ==> token_index = token_index.cuda()
target = y = target ==> target = target.cuda()
# 定义训练的轮数epoch
EPOCH = 5
# 参数保存后文件名
PATH = "mode.pt"
# torch.save用字典的形式保存训练相关的参数值
torch.save({
'epoch':EPOCH,
'model_state_dict':net.state_dict(),
'optimizer_state_dict':optimizer.state_dict(),
'Loss':loss,},PATH)
# file.pt 表示加载的模型参数文件
# 方式 1 :加载到GPU,默认GPU0
checkpoint = torch.load(file.pt,map_location=torch.device("cuda"))
# 方式 2 :加载到GPU1上
checkpoint = torch.load(file.pt,map_location=torch.device("cuda:1"))
# 方式 3: 加载到CPU上
checkpoint = torch.load(file.pt,map_location=torch.device("cpu"))
当我们用logging.warning()
,不能直接输出张量(影响程序性能)
,需要将张量(这里特指标量)转换成python类型输出;
import logging
acc = torch.Tensor([1.0])
# 张量输出以你选哪个性能,需要转换成python变量
logging.warning(f"acc={acc}") # 影响性能,应该避免
# 输出结果:WARNING:root:acc=tensor([1.])
# 将张量转换成python变量
logging.warning(f"acc.item()={acc.item()}") # 改善后
# 输出结果:WARNING:root:acc.item()=1.0
import logging
import torch
# 检测显卡是否可用,True 表示可用
if torch.cuda.is_available():
logging.warning("cuda is available!")
# 判断环境中多少个显卡。如果大于1就输出显卡个数,否则
if torch.cuda.device_count() > 1:
logging.warning(f"find{torch.cuda.device_count()}GPUS!")
else:
logging.warning("it is only one GPU!")
else:
logging.warning("cuda is not available,exit!")
# 代码设置环境变量
# 设置环境中第0个和第1个显卡可见
os.environ['CUDA_VISIBLE_DEVICES']="0,1"
# 命令行中实现设置
# python :解释器;
# xxxx.py :需要执行的文件
# CUDA_VISIBLE_DEVICES="0,1" python xxxx.py
直接将模型用nn.DataParallel包裹起来即可;
model = nn.DataParallel(model.cuda(),device_ids=[0,1,1,3])
data = data.cuda()
# 模型的保存
# Additional information
EPOCH = 5
PATH = "model.pt"
LOSS = 0.4
torch.save({
'epoch': EPOCH,
# 因为包裹了一个nn.DataParallel,多了一个module
'model_state_dict': model.module.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': LOSS,
}, PATH)
# 模型的加载
checkpoint = torch.load(resume,map_location=torch.device("cuda:0"))
# 方式1:通过代码设置
torch.cuda.set_device(args.local_rank) # 表示用第 local_rank 张卡
# 方式2:通过命令行设置环境变量
CUDA_VISIBLE_DEVICES="0,1"
torch.distributed.init_process_group("ncll",world_size=n_gpus,rank=args.local_rank)
# "ncll" : 表示GPU之间的通讯方式,推荐
# "world_size": 表示当前节点上有多少个GPU卡
# "rank":表示当前进程是在哪个GPU上
# args.local.rank是通过命令 python -m torch.distributed.launch --nproc-node=n_gpus train.py 出入的
因为存在单机多卡的情况,所以我们需要将train_datasets and eval_datasets
根据GPU的个数来进行不重叠分割来保证均等随机的分配到每个GPU上,这里我们用到pytorch官网上提供的类 DistributedSampler
来对train_datasets and eval_datasets
进行均匀随机分割;第一要做到均匀分割到不同GPU上,第二要将train_datasets and eval_datasets
里面的样本打散;
# 2.split the dataloader
from torch.utils.data.distributed import DistributedSampler
from torch.utils.data import DataLoader
def prepare(rank,world_size,batch_size=32,pin_memory=False,num_workers=0):
# Your_Dataset() 就是需要拆包的train_dataset and eval_dataset
dataset = Your_Dataset()
# num_replicas:GPU的个数
# rank:表示第 rank 个 GPU
# shuffle:是否需要打乱
# drop_last:表示是否将最后几个丢弃掉,保证样本个数是GPU个数的整数倍
sampler = DistributedSampler(dataset,num_replicas=world_size,
rank=rank,shuffle=False,drop_last=False)
dataloader = DataLoader(dataset,batch_size=batch_size,
pin_memory=pin_memory,num_workers=num_workers,drop_last=False,
shuffle=False,sampler=sampler)
return dataloader
data = data.cuda(args.local_rank)
local_rank
的参数选项,launch会传入这个参数train_sampler.set_epoch(epoch)
可以使得数据充分打乱# 是为了每张卡在每个周期中得到的数据是随机的,可以查看DistributedSampler类的set_epoch函数
train_sampler.set_epoch(epoch_index)
# from DistributedSampler.__iter__函数源码
# g = torch.Generator()
# g.manual_seed(self.seed + self.epoch)
# 可以看出随机种子设置为self.seed+self.epoch所以我们需要在每个周期中设置set_epoch
# 这样就可以保证随机种子不一样了。
sampler
就不要在DataLoader
中设置shuffle=True
了# 在命令行中解析相关参数
import argparse
parser=argparse.ArgumentParser()
parser.add_argument("--local_rank",help="local deviced on current node",type=int)
args = parser.parse_args()
n_gpus = 2
torch.distributed.init_process_groups("nccl",world_size=n_gpus,rank=args.local_rank)
torch.cuda.set_device(args.local_rank)
# 函数def train(xxxx,local_rank,train_datasests)
model = nn.Parallel.DistributedDataParallel(model.cuda(local_rank),devices_ids=[localrank])
# dataset_train分割
train_sampler=DistributedSampler(train_datasets)
train_data_loader=torch.utils.data.DataLoader(train_dataset,batch_size=BATCH_SIZE,collate_fn=collate_fn,
sampler=train_sampler)
eval_data_loader = torch.utils.data.DataLoader(eval_dataset,batch_size=8,collate_fn=collate_fn)
# 数据拷贝到GPU中
token_index = token_index.cuda(local_rank)
target=target.cuda(local_rank)
torch.nn.parallel.DistributedDataParallel
n_gpus
个GPUpython -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上
A Comprehensive Tutorial to Pytorch DistributedDataParallel
PyTorch Distributed Training