一、CPU 与 GPU
二、如何使用pytorch在CPU与GPU之间进行数据迁移
三、CUDA包中常用的方法
四、多GPU并行运算的机制
五、GPU模型加载过程中的常见问题
这节课来学习pytorch如何使用GPU。
CPU:中央处理器,主要包括控制器和运算器。
GPU:图像处理器。为什么成为图像处理器呢?因为在早起计算机中,处理统一的,五依赖的大规模数据运算,主要在图形、图像这个任务中,所以这个处理器就称为图形处理器。
示意图是两个处理器的差异。黄色为控制单元,绿色为计算单元,橙色为存储单元。我们可以看到,在GPU中,绿色面积占到绝大部分,而在CPU中,计算单元是比较少的。
处理器要进行数据运算,运算的两个数据必须要在同一个处理器上,要么都在GPU上,要么都在CPU上。(这个问题不知道对不对,视频中这么讲的,但是我实验发现也能运行。见【例4】)
在pytorch中,如何在CPU与GPU之间来回切换呢?
其实pytorch提供了一个非常简便的方法:to()。
从CPU放到GPU: data.to("cuda")
从GPU放到CPU: data.to("cpu")
data通常会有两种数据类型:1. Tensor张量 2. Module模型
to()函数除了可以转移设备,还可以转换数据类型。下面来学习这两种数据类型的to()函数。
1. tensor转换数据类型
x = torch.ones((3,3))
x = x.to(torch.float64) #把x的数据类型从float32位转换成float64位。
2. tensor转换设备
x = torch.ones((3,3))
x = x.to("cuda") #把X迁移到GPU上
注意:x2 = x.cuda() 这个方法也是将张量迁移到GPU中,但是这个方法是pytorch0.4.0之前的一个方法,现在已经弃用。
3. Module改变数据类型
linear = nn.Linear(2,2)
linear.to(torch.double) #linear中的所有数据类型都变成float64
4. Module改变设备
linear = nn.Linear(2,2)
gup1 = torch.device("cuda")
linear.to(gpu1) #迁移到GPU上
张量的to()函数与Module的to()有什么区别?
张量的to()函数不会执行inplace操作,因此需要用“=”进行赋值。【例1】。而Module的to()函数会执行inplace操作。【例2】
例1:
结果:
可以看到内存地址是不同的。说明张量的to()函数不会执行inplace操作。
例2:
结果:
例3:在GPU上运算得到的结果,还是在GPU上。
结果:
例4:如果模型在GPU上,数据在CPU上
结果:
跟视频讲的不一样。这就不知道了,可能自己转到GPU上了吧。
1. torch.cuda.device_count() #计算当前可见可用的GPU的数量
2. torch.cuda.get_device_name() #获取GPU的名称
3. torch.cuda.manual_seed() #为当前GPU设置随机种子
4. torch.cuda.manual_seed_all() #为所有可见可用gpu设置随机种子。
5. torch.cuda.set_device() #设置主gpu为哪一个物理gpu。这个方法不推荐使用,因为在多GPU的使用过程中,这个方法很容易产生混淆。推荐的方法是设置系统的环境变量: os.environ.setdefault("CUDA_VISIBLE_DEVICES", "2,3")。
"CUDA_VISIBLE_DEVICES"用来控制当前脚本可见的GPU的数量。怎么,理解呢?
物理GPU:实实在在插在显卡上的GPU。而逻辑GPU是Python可见的GPU。os.environ.setdefault("CUDA_VISIBLE_DEVICES", "2,3")这句话的意思:物理2号GPU作为逻辑0号GPU,物理3号GPU作为逻辑1号GPU。一共就这两个逻辑GPU。
再比如:os.environ.setdefault("CUDA_VISIBLE_DEVICES", "0,3,2")这句话的意思:物理0号GPU作为逻辑0号GPU,物理3号GPU作为逻辑1号GPU,物理2号GPU作为逻辑2号GPU。一共就这三个逻辑GPU。
我们为什么要煞费苦心的设计CUDA_VISIBLE_DEVICES呢?这是由于一个设备中可能有很多人在使用GPU。我们可以通过这个方法,合理地分配GPU。或者我们启动了多个实验,可以通过这个方法给不同实验分配不同GPU。
在逻辑GPU中,我们通常有一个主GPU的概念。通常默认为第0个GPU是主GPU。为什么要分主GPU这个概念呢?这与多GPU运算的分发并行机制有关。
共有三步:分发、并行计算、结果回收。
首先分发数据,然后每个GPU去运算,最后回收存储到主GPU上。
接下来学习在pytorch中如何实现多GPU的运算。很简单,只需要将我们的模型进行一个包装,包装成DataParallel这个数据类型就可以了。
device_ids: 要分发到哪些GPU。
output_device:输出设备,通常为主GPU。
例1:手动分配GPU。
# -*- coding: utf-8 -*-
import os
import numpy as np
import torch
import torch.nn as nn
# ============================ 手动选择gpu
gpu_list = [2,3]
gpu_list_str = ','.join(map(str, gpu_list))
os.environ.setdefault("CUDA_VISIBLE_DEVICES", gpu_list_str)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class FooNet(nn.Module):
def __init__(self, neural_num, layers=3):
super(FooNet, self).__init__()
self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
def forward(self, x):
print("\nbatch size in forward: {}".format(x.size()[0])) #在forward里面进行输出。看看x.size()[0]就是batch的大小。传进来的应该是分发后的数据,因此应该是batchsize除以GPU的数量。
for (i, linear) in enumerate(self.linears):
x = linear(x)
x = torch.relu(x)
return x
if __name__ == "__main__":
batch_size = 16
# data
inputs = torch.randn(batch_size, 3)
labels = torch.randn(batch_size, 3)
inputs, labels = inputs.to(device), labels.to(device) #放到GPU中
# model
net = FooNet(neural_num=3, layers=3)
net = nn.DataParallel(net) #将模型进行包装。这时候net就具备了并行分发的机制。只要把它放到GPU上,再进行前向传播。就可以自动地将一个batchsize的数据平均地分发到各个GPU上,进行并行计算
net.to(device)
# training
for epoch in range(1):
outputs = net(inputs)
print("model outputs.size: {}".format(outputs.size()))
print("CUDA_VISIBLE_DEVICES :{}".format(os.environ["CUDA_VISIBLE_DEVICES"]))
print("device_count :{}".format(torch.cuda.device_count()))
结果:
可以看到,batchsize大小为16,共有2个GPU在用,每个GPU上有8个样本。
如果将列表改为[1,2,3]
结果如下:
这时候就是用3个GPU了。
例2:自动分配。首先统计一下GPU的空闲情况,然后分配。
(1) 如何查看gpu剩余内存:
-q: 查询
-d Memory : 表示要查询内存
grep -A4 GPU 显示匹 配的一行以及它后面的4行
grep Free > tmp.txt 搜索内存剩余,得到的信息放到tmp.txt中。
打开tmp.txt文件,对于每一行,拆分,转成int。
(2) 整个自动设置的代码如下:
首先通过get_gpu_memory()获取gpu的内存剩余信息;
然后,对其进行排序。
转换成字符串的形式。
最后,就可以设置环境变量了。
例1:
# -*- coding: utf-8 -*-
import os
import numpy as np
import torch
import torch.nn as nn
# ============================ 依内存情况自动选择主gpu
def get_gpu_memory():
import platform
if 'Windows' != platform.system():
import os
os.system('nvidia-smi -q -d Memory | grep -A4 GPU | grep Free > tmp.txt')
memory_gpu = [int(x.split()[2]) for x in open('tmp.txt', 'r').readlines()]
os.system('rm tmp.txt')
else:
memory_gpu = False
print("显存计算功能暂不支持windows操作系统")
return memory_gpu
gpu_memory = get_gpu_memory()
if gpu_memory:
print("\ngpu free memory: {}".format(gpu_memory))
gpu_list = np.argsort(gpu_memory)[::-1]
gpu_list_str = ','.join(map(str, gpu_list))
os.environ.setdefault("CUDA_VISIBLE_DEVICES", gpu_list_str)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class FooNet(nn.Module):
def __init__(self, neural_num, layers=3):
super(FooNet, self).__init__()
self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
def forward(self, x):
print("\nbatch size in forward: {}".format(x.size()[0])) #在forward里面进行输出。看看x.size()[0]就是batch的大小。传进来的应该是分发后的数据,因此应该是batchsize除以GPU的数量。
for (i, linear) in enumerate(self.linears):
x = linear(x)
x = torch.relu(x)
return x
if __name__ == "__main__":
batch_size = 16
# data
inputs = torch.randn(batch_size, 3)
labels = torch.randn(batch_size, 3)
inputs, labels = inputs.to(device), labels.to(device) #放到GPU中
# model
net = FooNet(neural_num=3, layers=3)
net = nn.DataParallel(net) #将模型进行包装。这时候net就具备了并行分发的机制。只要把它放到GPU上,再进行前向传播。就可以自动地将一个batchsize的数据平均地分发到各个GPU上,进行并行计算
net.to(device)
# training
for epoch in range(1):
outputs = net(inputs)
print("model outputs.size: {}".format(outputs.size()))
print("CUDA_VISIBLE_DEVICES :{}".format(os.environ["CUDA_VISIBLE_DEVICES"]))
print("device_count :{}".format(torch.cuda.device_count()))
结果:
尝试在一个CUDA不可用的设备上,进行模型反序列化。模型是以CUDA的形式进行保存的。
也就是说,模型在GPU上进行训练之后,保存下来之后,想要在一个CPU上进行加载。
解决:torch.load()设置map_location参数为cpu。这样就可以在CPU设备上加载GPU模型了。
例:
# -*- coding: utf-8 -*-
import os
import numpy as np
import torch
import torch.nn as nn
class FooNet(nn.Module):
def __init__(self, neural_num, layers=3):
super(FooNet, self).__init__()
self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
def forward(self, x):
print("\nbatch size in forward: {}".format(x.size()[0]))
for (i, linear) in enumerate(self.linears):
x = linear(x)
x = torch.relu(x)
return x
gpu_list = [0]
gpu_list_str = ','.join(map(str, gpu_list))
os.environ.setdefault("CUDA_VISIBLE_DEVICES", gpu_list_str)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
net = FooNet(neural_num=3, layers=3)
net.to(device)
# save
net_state_dict = net.state_dict()
path_state_dict = "./model_in_gpu_0.pkl"
torch.save(net_state_dict, path_state_dict)
# load
state_dict_load = torch.load(path_state_dict)
# state_dict_load = torch.load(path_state_dict, map_location="cpu")
print("state_dict_load:\n{}".format(state_dict_load))
结果:
因为是在CPU上进行训练保存的,所以加载之后还是在GPU上。
如果本地没有GPU的话,加载的时候就会上面的错误,这时候需要加入参数,改成下面这句。
结果:
这样就放到CPU上了。
由于训练时候,采用多GPU并行运算,所以模型被DataParallel包装。这就使得我们模型的网络层的命名会多了一个“module”,所以导致在加载state_dict的时候,字典的命名不匹配。
可以通过下面的5行代码,将state_dict中的key进行修改。
构建一个新的OrderedDict();然后对加载进来的state_dict进行for循环,如果k以"module"开头,就修改一下。然后放到新的state_dict中。
例:
# -*- coding: utf-8 -*-
import os
import numpy as np
import torch
import torch.nn as nn
class FooNet(nn.Module):
def __init__(self, neural_num, layers=3):
super(FooNet, self).__init__()
self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
def forward(self, x):
print("\nbatch size in forward: {}".format(x.size()[0]))
for (i, linear) in enumerate(self.linears):
x = linear(x)
x = torch.relu(x)
return x
# =================================== 多gpu 保存
gpu_list = [0, 1, 2, 3]
gpu_list_str = ','.join(map(str, gpu_list))
os.environ.setdefault("CUDA_VISIBLE_DEVICES", gpu_list_str)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
net = FooNet(neural_num=3, layers=3)
net = nn.DataParallel(net)
net.to(device)
# save
net_state_dict = net.state_dict()
path_state_dict = "./model_in_multi_gpu.pkl"
torch.save(net_state_dict, path_state_dict)
# =================================== 多gpu 加载
net1 = FooNet(neural_num=3, layers=3)
path_state_dict = "./model_in_multi_gpu.pkl"
state_dict_load = torch.load(path_state_dict, map_location="cpu")
print("state_dict_load:\n{}".format(state_dict_load))
# net1.load_state_dict(state_dict_load) #如果使用这一句进行加载会报错。
# remove module.
from collections import OrderedDict
new_state_dict = OrderedDict()
for k, v in state_dict_load.items():
namekey = k[7:] if k.startswith('module.') else k
new_state_dict[namekey] = v
print("new_state_dict:\n{}".format(new_state_dict))
net1.load_state_dict(new_state_dict)
结果: