| 更新:2020.10.25 | [email protected]
前言:本博客基本涵盖single-gpu和multi-gpu的使用,及训练模型的保存和加载。更复杂功能,修改后亦可得到。
查看gpu使用情况和哪些用户在使用gpu:(watch -n [time] nvidia-smi)和(gpustat -cpu)
https://github.com/wookayin/gpustat
https://pypi.org/project/gpustat/
关闭服务器 GPU 占用线程:kill -9 PID
注意:Train/Test过程中 inputs 和 labels,以及待训练 model 均加载到GPU中。对小模型来说,多GPU并行运算反而耗时,大模型bath_size远大于GPU数(或加宽加深Hidden-layers),GPU优势才能体现。增大bath_size,导致预测准确率降低,可增大epoch。
因为pytorch是在第0块gpu上初始化,占用一定空间的显存,所以使用不当会遇到out of memory的问题。以下探讨涵盖single-GPU和Multi-GPU在训练前指定GPU、保存和加载训练模型、GPU和CPU互加载模型三个过程。
直接使用代码 model.cuda(), PyTorch默认从0开始的单GPU:
model = Model()
if torch.cuda.is_available():
model = model.cuda()
有两种方法可直接指定单GPU:
CUDA_VISIBLE_DEVICES=1 python main.py
,表示只有第1块gpu可见,其他gpu不可用。第1块gpu编号已变成第0块,如果依然使用cuda:1
会报invalid device ordinal;以下同效。 os.environ["CUDA_VISIBLE_DEVICES"] = "1" # 官方推荐使用 "CUDA_VISIBLE_DEVICES"
model = Model()
if torch.cuda.is_available():
model = model.cuda() #使用第一个GPU
images = images.cuda()
labels = labels.cuda()
or # 直接定义设备device,并指定起始位置GPU:"cuda:0"。或"cuda:1"作为起始位置,编号为0
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 单GPU运行,且多GPU时可指定起始位置/编号
net = self.model.to(device) # 等效于self.model.cuda()
images = self.images.to(device)
labels = self.labels.to(device)
Note,“cuda:0"或"cuda"都代表起始device_id为0,系统默认从0开始。可根据需要修改起始位置,如“cuda:1”等效"cuda:0"或"cuda”。
# 任取一个,torch版本不同会有差别
torch.cuda.device(id) # id 是GPU编号
or
torch.cuda.set_device(id)
or
torch.device('cuda')
state = {'model': self.model.state_dict(), 'epoch': ite}
torch.save(state, self.model.name())
or # 直接保存
torch.save(self.model.state_dict(), 'Mymodel.pth') # 当前目录
3. PyTorch使用指定GPU训练 - 其他问题详解(含CPU)
) checkpoint = torch.load(self.model.name())
self.model.load_state_dict(checkpoint['model'])
or # 直接加载
self.model.load_state_dict(torch.load('Mymodel.pth'))
or # load gpu or cpu
if torch.cuda.is_available(): # gpu
self.model.load_state_dict(torch.load('Mymodel.pth'))
else: # cpu 官方推荐CPU的加载方式
checkpoint = torch.load(self.model.name(),map_location=lambda storage, loc: storage)
self.model.load_state_dict(checkpoint['model'])
仍有两种方法可直接指定多GPU:
CUDA_VISIBLE_DEVICES=0,1,3 python main.py
# gpu_ids = [0, 1, 3] # 或 os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,3"
# os.environ["CUDA_VISIBLE_DEVICES"] = ','.join(map(str, [0, 1, 3]))
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,3" # CUDA_VISIBLE_DEVICES 表当前可被python程序检测到的显卡
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 多GPU时可指定起始位置/编号
# 若不加if项,也不报错,但训练可能会变成单GPU
if torch.cuda.device_count() > 1: # 查看当前电脑可用的gpu数量,或 if len(gpu_ids) > 1:
print("Let's use", torch.cuda.device_count(), "GPUs!")
# self.model = torch.nn.DataParallel(self.model, device_ids=gpu_ids)
self.model = torch.nn.DataParallel(self.model) # 声明所有设备
net = self.model.to(device) # 从指定起始位置开始,将模型放到gpu或cpu上
images = self.images.to(device) # 模型和训练数据都放在主设备
labels = self.labels.to(device)
Note:使用多GPU训练,单用 model = torch.nn.DataParallel(model),默认所有存在的显卡都会被使用。
if isinstance(self.model,torch.nn.DataParallel): # 判断是否并行
self.model = self.model.module
state = {'model': self.model.state_dict(), 'epoch': ite}
torch.save(state, self.model.name()) # No-module
or
if isinstance(self.model, torch.nn.DataParallel):
torch.save(self.model.module.stat_dict, 'Mymodel') # No-module
else:
torch.save(self.model.stat_dict, 'Mymodel') # No-module
or # 直接保存
torch.save(self.model.state_dict(), 'Mymodel.pth') # is-module
3. PyTorch使用指定GPU训练 - 其他问题详解(含CPU)
) # ################## 方法 1: add
net = torch.nn.DataParallel(net) # 加上module
net.load_state_dict(torch.load("model/cnn_train.pth")) # 加上module,再加载model
# ################## 方法 2: remove (2选1)
net.load_state_dict({k.replace('module.', ''): v for k, v in torch.load("model/cnn_train.pth").items()})
or
from collections import OrderedDict
state_dict = torch.load("model/cnn_train.pth") # 当前路径 model 文件下
new_state_dict = OrderedDict() # create new OrderedDict that does not contain `module.`
for k, v in state_dict.items(): # remove `module.`
name = k[7:] # 或 name = k.replace('module.', '')
new_state_dict[name] = v
net.load_state_dict(new_state_dict)
torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)
Note:多GPU训练使用DataParallel对网络进行封装,因此在原网络结构中添加了一层module。
module
:多GPU并行处理的模型device_ids
:GPU编号(默认全部GPU)output_device
:输出位置(默认device_ids[0]或cuda:0)dim
:tensors被分散的维度,默认0 gpu_ids = [3, 4, 6, 7] # 或os.environ["CUDA_VISIBLE_DEVICES"] = "3,4,6,7"
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 多GPU时可指定起始位置/编号
# 若不加if项,也不报错,但训练可能会变成单GPU
if torch.cuda.device_count() > 1: # 查看当前电脑可用的gpu数量,或 if len(gpu_ids) > 1:
print("Let's use", torch.cuda.device_count(), "GPUs!")
self.model = torch.nn.DataParallel(self.model, device_ids=gpu_ids) # 声明所有可用设备
net = self.model.to(device) # 模型放在主设备
images = self.images.to(device) # 训练数据放在主设备
labels = self.labels.to(device)
model = Net() # 在单GPU中
out = model.fc(input)
model = Net() # 在DataParallel中,调用并行网络中定义的网络层
model = torch.nn.DataParallel(model)
out = model.module.fc(input)
# 假设只保存了模型的参数(model.state_dict())到文件名为modelparameters.pth, model = Net()
# cpu -> cpu or gpu -> gpu:
checkpoint = torch.load('modelparameters.pth')
model.load_state_dict(checkpoint)
# cpu -> gpu 1
torch.load('modelparameters.pth', map_location=lambda storage, loc: storage.cuda(1))
# gpu 1 -> gpu 0
torch.load('modelparameters.pth', map_location={'cuda:1':'cuda:0'})
# gpu -> cpu
torch.load('modelparameters.pth', map_location=lambda storage, loc: storage)
# 特殊情况
torch.load(opt.model,map_location='cpu')
# coding: utf-8
# coding: GBK
import torch
import torchvision
import torchvision.transforms as transforms
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
from torch.autograd import Variable
from torch.backends import cudnn
# 若使用服务器多卡训练
import os
from collections import OrderedDict
# 指定对程序可见的GPU编号
# 表示只有第0,1,3块GPU可见,其他GPU不可用,并且第1块GPU默认编号就是第0块
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,3'
# torch.cuda.current_device()
# torch.cuda.initialized = True
# 定义数据转换transformer
transform = transforms.Compose(
[transforms.ToTensor(), # (H,W,C)转换为(C,H,W) 并且值为[0, 1.]
# transforms.Resize((32, 32)),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))] # 归一化
)
# 加载数据
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=10, shuffle=True, num_workers=0)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=10, shuffle=False, num_workers=0)
classes = ['plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
# ############################################################ 定义网络 简单的CNN
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
h1 = self.pool(F.relu(self.conv1(x)))
h2 = self.pool(F.relu(self.conv2(h1)))
h2 = h2.view(-1, 16 * 5 * 5)
h3 = self.fc1(h2)
h4 = self.fc2(h3)
h5 = self.fc3(h4)
return h5
# 实例化模型
net = CNN()
# 使用(多)GPU训练
# 定义device,“cuda:0” 只代表起始的device_id为 0
device = torch.device('cuda:2' if torch.cuda.is_available() else 'cpu')
print("GPU or CPU is available: ", device)
if torch.cuda.device_count() > 1: # multi-gpu
print('Lets use', torch.cuda.device_count(), 'GPUs!')
net = nn.DataParallel(net)
net.to(device)
# 定义损失函数(loss function)和优化器(optimizer)
criterion = nn.CrossEntropyLoss() # classification criterion and regression criterion
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
# ############################################################ 训练
net.train() # 在训练时启用BN层和Dropout层,对模型进行更改
for epoch in range(1): # 循环遍历数据集的次数
running_loss = 0.
# enumerate 将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,并列出数据和数据下标,常用于for
for i, data in enumerate(train_loader, 0):
images, labels = data # get the inputs
# 需要将输入网络的数据复制到GPU
images = images.to(device)
labels = labels.to(device)
optimizer.zero_grad() # 清空过往梯度缓存区域
# 经典四步
outs = net(images)
loss = criterion(outs, labels) # forward,前向传播
loss.backward() # backward,后向传播,计算当前梯度
optimizer.step() # optimize,根据梯度更新网络参数
# 打印loss
running_loss += loss.item()
if i % 2000 == 1999: # print every 2000 mini-batches
print('[epoch %d, iter %d] loss : %.3f' % (epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.
print('Finish Training!')
torch.save(net.state_dict(), 'model/cnn_train.pth') # multi-gpu has module,single-gpu or cpu has No-module
print('Finish save the model!')
# ############################################################ 测试
# os.environ['CUDA_VISIBLE_DEVICES'] = '0'
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') # train,device:(cuda:0-n);test,device: (cuda:0)
print('Test is running:', device)
# ################## load 'model.pth'
# def load_gpu_cpu_pth(self, net, path):
# single-gpu, multi-gpu and cpu, auto - load and convert types in 'model.pth'
if 'gpu' if torch.cuda.is_available() else 'cpu' == 'gpu':
state_dict = torch.load("model/cnn_train.pth")
else:
state_dict = torch.load("model/cnn_train.pth", map_location=lambda storage, loc: storage)
new_state_dict = OrderedDict() # create new OrderedDict that does not contain `module.`
if isinstance(net, torch.nn.DataParallel): # 判断模型net是否并行
print('\nThe source model is isinstance in test')
if list(state_dict.keys())[0][:6] == 'module':
print("The loaded model always contains 'module'")
# 直接加载 -- Model is module
net.load_state_dict(state_dict)
else:
print("The loaded model is adding 'module'...")
# Method 1: add -- Model is No-module
net = torch.nn.DataParallel(net) # add module
net.load_state_dict(state_dict) # then load model
print("Finish loading 'model.pth'\n")
else:
print('\nThe source model is not isinstance in test')
if list(state_dict.keys())[0][:6] == 'module':
print("The loaded model is removing 'module'")
# Method 2: remove (2选1)
# net.load_state_dict({k.replace('module.', ''): v for k, v in torch.load("model/cnn_train.pth").items()})
for k, v in state_dict.items(): # remove `module.`
name = k[7:] # 或 name = k.replace('module.', '')
new_state_dict[name] = v
net.load_state_dict(new_state_dict)
else:
print("The loaded model always contains 'module'")
# 直接加载 -- Model is No-module
net.load_state_dict(state_dict)
print("Finish loading 'model.pth'\n")
# ################## test
net.to(device)
net.eval() # 在评测时不启用BN层和Dropout层,冻结后这两个操作不会对模型进行更改
correct_test = 0
total_test = 0
for epoch in range(1): # range(start, stop[, step]),默认从0开始,range(0)是空集
for data in test_loader:
images_test, labels_test = data
# 需要将测试网络的数据复制到GPU
images_test = images_test.to(device)
labels_test = labels_test.to(device)
# 评估预测
# 虽然使用net.eval(),但在验证阶段有时报错out of memory,可能是梯度不回传,造成梯度累加。故取消验证阶段的loss。
with torch.no_grad():
outs_test = net(images_test)
_, predict = torch.max(outs_test.data, 1)
total_test += labels_test.size(0)
correct_test += (predict == labels_test).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct_test / total_test))
print('Finish Testing!')
[1] CPU加载GPU训练model和GPU加载CPU训练model:
https://www.ptorch.com/news/74.html
[2] 单机多卡并行训练、多机多GPU训练和DistributedDataParallel解决显存使用不平衡:
https://blog.csdn.net/weixin_47196664/article/details/106542016?utm_medium=distribute.wap_relevant.none-task-blog-title-2