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 #查看模型参数存储在哪个设备上面