李沐动手学深度学习V2-GPU在Pytorch中使用

1.GPU在Pytorch中使用

在PyTorch中,每个数组都有一个设备(device), 我们通常将其称为上下文(context)。 默认情况下,所有变量和相关的计算都分配给CPU。 有时上下文可能是GPU。 当我们跨多个服务器部署作业时,事情会变得更加棘手。 通过智能地将数组分配给上下文, 我们可以最大限度地减少在设备之间传输数据的时间。 例如,当在带有GPU的服务器上训练神经网络时, 我们通常希望模型的参数在GPU上。

1.1使用nvidia-smi命令来查看显卡信息。

! nvidia-smi #jupyter notebook查看gpu信息命令
输出结果:
Sun Apr 24 18:39:52 2022       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 471.99       Driver Version: 471.99       CUDA Version: 11.4     |
|-------------------------------+----------------------+----------------------+
| GPU  Name            TCC/WDDM | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. | 
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA GeForce ... WDDM  | 00000000:01:00.0 Off |                  N/A |
| N/A   47C    P0    17W /  N/A |    106MiB /  4096MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

在这里插入图片描述

1.2计算设备

在PyTorch中,CPU和GPU可以用torch.device(‘cpu’) 和torch.device(‘cuda’)表示。 应该注意的是,cpu设备意味着所有物理CPU和内存, 这意味着PyTorch的计算将尝试使用所有CPU核心。 然而,gpu设备只代表一个卡和相应的显存。 如果有多个GPU,我们使用torch.device(f’cuda:{i}') 来表示第 𝑖 块GPU( 𝑖 从0开始)。 另外,cuda:0和cuda是等价的。
我们可以指定用于存储和计算的设备,如CPU和GPU。 默认情况下,张量是在内存中创建的,然后使用CPU计算它。

import torch
from torch import nn
torch.device('cpu'),torch.device('cuda'),torch.device('cuda:0'),torch.device('cuda:1')#cuda:0表示第1个gpu,cuda:1表示第2个gpu
torch.cuda.device_count()#查询gpu个数
'''
输出结果:
(device(type='cpu'),
 device(type='cuda'),
 device(type='cuda', index=0),
 device(type='cuda', index=1))
 个数1个
'''
def get_gpu(i=0):
    #如果存在,则返回gpu(i),否则返回cpu()
    if torch.cuda.device_count()>= i+1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')
#返回所有可用的GPU,如果没有GPU,则返回[cpu(),]
def get_all_gpu():
    devices = [torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count())]
    return devices if devices else [torch.device('cpu')] #返回值为列表形式
get_gpu(),get_gpu(10),get_all_gpu()
'''
输出结果:
(device(type='cuda', index=0),
 device(type='cpu'),
 [device(type='cuda', index=0)])
'''

1.3 查询张量所在的设备

默认情况下,张量是在内存中创建的,然后使用CPU计算它。
注意:无论何时我们要对多个项进行操作, 它们都必须在同一个设备上。 例如,如果我们对两个张量求和, 我们需要确保两个张量都位于同一个设备上, 否则框架将不知道在哪里存储结果,甚至不知道在哪里执行计算。

X = torch.tensor([1,2,3])
X.device #查询张量所在的设备
'''
输出结果:
device(type='cpu')
'''

1.4 在GPU上面创建张量

我们可以在创建张量时指定存储设备。接 下来,我们在第一个gpu上创建张量变量X。 在GPU上创建的张量只消耗这个GPU的显存。 可以使用nvidia-smi命令查看显存使用情况。 一般来说,需要确保不创建超过GPU显存限制的数据。

Y = torch.tensor([1,2,3],device = get_gpu())#在GPU上面创建张量
#Y = torch.tensor([1,2,3],device = torch.device('cuda')) 与上面代码等价
Y.device,Y
#结果:(device(type='cuda', index=0), tensor([1, 2, 3], device='cuda:0'))
Y1 = torch.rand(2, 3, device=get_gpu(0))
Y1
#结果:tensor([[0.6150, 0.2178, 0.4462],
        [0.8411, 0.1061, 0.0564]], device='cuda:0')

1.5 张量复制

当张量位于不同设备时,需要进行计算,需要把它们都复制到同一个设备上才能进行计算。
如果我们[要计算X + Y,我们需要决定在哪里执行这个操作]。 例如, 我们可以将X传输到第二个GPU并在那里执行操作。 不要简单地X加上Y,因为这会导致异常, 运行时引擎不知道该怎么做:它在同一设备上找不到数据会导致失败。 由于Y位于第二个GPU上,所以我们需要将X移到那里, 然后才能执行相加运算。

X_clone = X.cuda(0)#将X复制到gpu0上面
X_clone
#结果:tensor([1, 2, 3], device='cuda:0')
X.cuda(0) + Y ,X ,X_clone+Y #X.cuda(0)并没有把X所在的cpu转移到gpu上面,而是相当于将X复制了一遍并转移到gpu上面,而没有改变X原先所在的设备
'''
结果:(tensor([[1.8788, 2.1597, 3.0925],
         [1.0752, 2.2283, 3.6227]], device='cuda:0'),
 tensor([1, 2, 3]),
 tensor([[1.8788, 2.1597, 3.0925],
         [1.0752, 2.2283, 3.6227]], device='cuda:0'))
'''
Y.cuda(0) is Y #Y已经存储在gpu0上,再次调用cuda(0)并不会再次复制一遍,分配新内存,而是直接返回Y
#结果:True

注意:人们使用GPU来进行机器学习,因为单个GPU相对运行速度快。 但是在设备(CPU、GPU和其他机器)之间传输数据比计算慢得多。 这也使得并行化变得更加困难,因为我们必须等待数据被发送(或者接收), 然后才能继续进行更多的操作。 这就是为什么拷贝操作要格外小心。 根据经验,多个小操作比一个大操作糟糕得多。 此外,一次执行几个操作比代码中散布的许多单个操作要好得多(除非你确信自己在做什么)。 如果一个设备必须等待另一个设备才能执行其他操作, 那么这样的操作可能会阻塞。 这有点像排队订购咖啡,而不像通过电话预先订购: 当你到店的时候,咖啡已经准备好了。
最后,当我们打印张量或将张量转换为NumPy格式时, 如果数据不在内存中,框架会首先将其复制到内存中, 这会导致额外的传输开销。 更糟糕的是,它现在受制于全局解释器锁,使得一切都得等待Python完成。

1.6 在模型网络中使用张量

net = nn.Sequential(nn.Linear(3,1))
net = net.to(device=get_gpu())
net(Y)#将模型参数放在gpu上面,同时也要保证输入数据也在gpu上面,并且为同一个gpu设备上面
'''
结果:tensor([[-0.0342],
       [-0.5330]], device='cuda:0', grad_fn=<AddmmBackward0>)
'''
net[0].weight.data.device #查看模型参数存储在哪个设备上面
#结果:device(type='cuda', index=0)

2.总结

只要所有的数据和模型参数都在同一个设备上, 我们就可以有效地学习模型。
2.1 我们可以指定用于存储和计算的设备,例如CPU或GPU。默认情况下,数据在主内存中创建,然后使用CPU进行计算。
2.2 深度学习框架要求计算的所有输入数据都在同一设备上,无论是CPU还是GPU。
2.3 不经意地移动数据可能会显著降低性能。一个典型的错误如下:计算GPU上每个小批量的损失,并在命令行中将其报告给用户(或将其记录在NumPy ndarray中)时,将触发全局解释器锁,从而使所有GPU阻塞。最好是为GPU内部的日志分配内存,并且只移动较大的日志。

3. 全部源代码:

#在jupyter notebook中查看gpu信息指令
# ! nvidia-smi 

import torch
from torch import nn
torch.device('cpu'),torch.device('cuda'),torch.device('cuda:0'),torch.device('cuda:1')#cuda:0表示第1个gpu,cuda:1表示第2个gpu

torch.cuda.device_count()#查询gpu个数


def get_gpu(i=0):
    #如果存在,则返回gpu(i),否则返回cpu()
    if torch.cuda.device_count()>= i+1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')
#返回所有可用的GPU,如果没有GPU,则返回[cpu(),]
def get_all_gpu():
    devices = [torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count())]
    return devices if devices else [torch.device('cpu')] #返回值为列表形式

get_gpu(),get_gpu(10),get_all_gpu()

X = torch.tensor([1,2,3])
X.device #查询张量所在的设备

Y = torch.tensor([1,2,3],device = get_gpu())#在GPU上面创建张量
#Y = torch.tensor([1,2,3],device = torch.device('cuda')) 与上面代码等价
Y.device,Y

Y1 = torch.rand(2, 3, device=get_gpu(0))
Y1

X_clone = X.cuda(0)#将X复制到gpu0上面
X_clone

X.cuda(0) + Y ,X ,X_clone+Y #X.cuda(0)并没有把X所在的cpu转移到gpu上面,而是相当于将X复制了一遍并转移到gpu上面,而没有改变X原先所在的设备

Y.cuda(0) is Y #Y已经存储在gpu0上,再次调用cuda(0)并不会再次复制一遍,分配新内存,而是直接返回Y

net = nn.Sequential(nn.Linear(3,1))
net = net.to(device=get_gpu())
net(Y)#将模型参数放在gpu上面,同时也要保证输入数据也在gpu上面

net[0].weight.data.device #查看模型参数存储在哪个设备上面


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值