11.3 GPU使用

一、CPU 与 GPU 

二、如何使用pytorch在CPU与GPU之间进行数据迁移

三、CUDA包中常用的方法

四、多GPU并行运算的机制

五、GPU模型加载过程中的常见问题

 

这节课来学习pytorch如何使用GPU。

 

一、CPU 与 GPU 

11.3 GPU使用_第1张图片

CPU:中央处理器,主要包括控制器和运算器。

GPU:图像处理器。为什么成为图像处理器呢?因为在早起计算机中,处理统一的,五依赖的大规模数据运算,主要在图形、图像这个任务中,所以这个处理器就称为图形处理器。

示意图是两个处理器的差异。黄色为控制单元,绿色为计算单元,橙色为存储单元。我们可以看到,在GPU中,绿色面积占到绝大部分,而在CPU中,计算单元是比较少的。

 

二、如何使用pytorch在CPU与GPU之间进行数据迁移

处理器要进行数据运算,运算的两个数据必须要在同一个处理器上,要么都在GPU上,要么都在CPU上。(这个问题不知道对不对,视频中这么讲的,但是我实验发现也能运行。见【例4】)

在pytorch中,如何在CPU与GPU之间来回切换呢?

11.3 GPU使用_第2张图片

其实pytorch提供了一个非常简便的方法:to()。

从CPU放到GPU:  data.to("cuda")

从GPU放到CPU: data.to("cpu")

data通常会有两种数据类型:1. Tensor张量  2. Module模型

to()函数除了可以转移设备,还可以转换数据类型。下面来学习这两种数据类型的to()函数。

11.3 GPU使用_第3张图片

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:

11.3 GPU使用_第4张图片

结果:

可以看到内存地址是不同的。说明张量的to()函数不会执行inplace操作。

 

例2:

11.3 GPU使用_第5张图片

结果:

 

例3:在GPU上运算得到的结果,还是在GPU上。

11.3 GPU使用_第6张图片

结果:

 

例4:如果模型在GPU上,数据在CPU上

11.3 GPU使用_第7张图片

结果:

跟视频讲的不一样。这就不知道了,可能自己转到GPU上了吧。

 

三、CUDA包中常用的方法

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。

11.3 GPU使用_第8张图片

 

再比如:os.environ.setdefault("CUDA_VISIBLE_DEVICES", "0,3,2")这句话的意思:物理0号GPU作为逻辑0号GPU,物理3号GPU作为逻辑1号GPU,物理2号GPU作为逻辑2号GPU。一共就这三个逻辑GPU。

11.3 GPU使用_第9张图片

 

我们为什么要煞费苦心的设计CUDA_VISIBLE_DEVICES呢?这是由于一个设备中可能有很多人在使用GPU。我们可以通过这个方法,合理地分配GPU。或者我们启动了多个实验,可以通过这个方法给不同实验分配不同GPU。

 

四、多GPU并行运算的机制

在逻辑GPU中,我们通常有一个主GPU的概念。通常默认为第0个GPU是主GPU。为什么要分主GPU这个概念呢?这与多GPU运算的分发并行机制有关。

11.3 GPU使用_第10张图片

共有三步:分发、并行计算、结果回收。

首先分发数据,然后每个GPU去运算,最后回收存储到主GPU上。

 

接下来学习在pytorch中如何实现多GPU的运算。很简单,只需要将我们的模型进行一个包装,包装成DataParallel这个数据类型就可以了。

11.3 GPU使用_第11张图片

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()))



 

结果:

11.3 GPU使用_第12张图片

可以看到,batchsize大小为16,共有2个GPU在用,每个GPU上有8个样本。

 

如果将列表改为[1,2,3]

结果如下:

11.3 GPU使用_第13张图片

这时候就是用3个GPU了。

 

例2:自动分配。首先统计一下GPU的空闲情况,然后分配。

(1) 如何查看gpu剩余内存:

11.3 GPU使用_第14张图片

-q: 查询

-d Memory : 表示要查询内存

grep -A4 GPU 显示匹 配的一行以及它后面的4行

grep Free > tmp.txt   搜索内存剩余,得到的信息放到tmp.txt中。

打开tmp.txt文件,对于每一行,拆分,转成int。

 

(2) 整个自动设置的代码如下:

11.3 GPU使用_第15张图片

首先通过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()))







 

结果:

11.3 GPU使用_第16张图片

 

五、GPU模型加载过程中的常见问题

 

1. 错误1:

11.3 GPU使用_第17张图片

 

尝试在一个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))



 

结果:

11.3 GPU使用_第18张图片

因为是在CPU上进行训练保存的,所以加载之后还是在GPU上。

如果本地没有GPU的话,加载的时候就会上面的错误,这时候需要加入参数,改成下面这句。

结果:

11.3 GPU使用_第19张图片

这样就放到CPU上了。

 

2 错误2:

11.3 GPU使用_第20张图片

由于训练时候,采用多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)


 

结果:

11.3 GPU使用_第21张图片

 

 

你可能感兴趣的:(11,Python/DL/ML)