在 PyTorch
中以下数据结构分为 CPU
和 GPU
两个版本:
Tensor
-
nn.Module
(包括常用的layer
、loss function
,以及容器Sequential
等)
它们都带有一个 .cuda
方法,调用此方法即可将其转为对应的 GPU
对象。
注意,tensor.cuda
会返回一个新对象,这个新对象的数据已转移至GPU,而之前的 tensor
还在原来的设备上(CPU
)。而 module.cuda
则会将所有的数据都迁移至 GPU
,并返回自己。所以 module = module.cuda()
和 module.cuda()
所起的作用一致。
tensor = t.Tensor(3, 4)
# 返回一个新的tensor,但原来的tensor并没有改变
tensor.cuda(0)
tensor.is_cuda # False
重新赋给自己,tensor
指向 cuda
上的数据,不再执行原数据。不指定使用的 GPU
设备,将默认使用第 1 块 GPU
。
tensor = tensor.cuda()
tensor.is_cuda # True
nn.Module
在 GPU
与 CPU
之间的转换,本质上还是利用了 Tensor
在 GPU
和 CPU
之间的转换。nn.Module
的 cuda
方法是将 nn.Module
下的所有 parameter
(包括子 module
的 parameter
)都转移至GPU
,而 Parameter
本质上也是 tensor
( Tensor
的子类)。
# 将nn.Module模型放到cuda上,其子模型也都自动放到cuda上
from torch import nn
module = nn.Linear(3, 4)
module.cuda(device = 0)
module.weight.is_cuda # True
注意: 为什么将数据转移至 GPU
的方法叫做 .cuda
而不是 .gpu
,就像将数据转移至 CPU
调用的方法是 .cpu
?
这是因为 GPU
的编程接口采用 CUDA
,而目前并不是所有的 GPU
都支持 CUDA
,只有部分 Nvidia
的GPU
才支持。PyTorch
未来可能会支持 AMD
的 GPU
,而 AMD GPU
的编程接口采用 OpenCL
,因此PyTorch
还预留着 .cl
方法,用于以后支持 AMD
等的 GPU
。
下面将举例说明,这部分代码需要你具有两块GPU设备。
class VeryBigModule(nn.Module):
def __init__(self):
super(VeryBigModule, self).__init__()
self.GiantParameter1 = t.nn.Parameter(t.randn(100000, 20000)).cuda(0)
self.GiantParameter2 = t.nn.Parameter(t.randn(20000, 100000)).cuda(1)
def forward(self, x):
x = self.GiantParameter1.mm(x.cuda(0))
x = self.GiantParameter2.mm(x.cuda(1))
return x
上面最后一部分中,两个 Parameter
所占用的内存空间都非常大,大概是 8 个 G,如果将这两个都同时放在一块 GPU
上几乎会将显存占满,无法再进行任何其它运算。此时可通过这种方式将不同的计算分布到不同的 GPU
中。
关于使用 GPU
的一些建议:
-
GPU
运算很快,但对于很小的运算量来说,并不能体现出它的优势,因此对于一些简单的操作可直接利用CPU
完成; - 数据在
CPU
和GPU
之间,以及GPU
与GPU
之间的传递会比较耗时,应当尽量避免; - 在进行低精度的计算时,可以考虑
HalfTensor
,它相比于FloatTensor
能节省一半的显存,但需千万注意数值溢出的情况;
而除了调用对象的 .cuda
方法之外,还可以使用 torch.cuda.device
,来指定默认使用哪一块 GPU
,或使用torch.set_default_tensor_type
使程序默认使用 GPU
,不需要手动调用 cuda
。
# 如果未指定使用哪块GPU,默认使用GPU 0
x = t.cuda.FloatTensor(2, 3)
# x.get_device() == 0
y = t.FloatTensor(2, 3).cuda()
# y.get_device() == 0
# 指定默认使用GPU 0
with t.cuda.device(0):
# 在GPU 0上构建tensor
a = t.cuda.FloatTensor(2, 3)
# 将tensor转移至GPU 0
b = t.FloatTensor(2, 3).cuda()
print(a.get_device() == b.get_device() == 0 ) # True
c = a + b
print(c.get_device() == 0) # True
z = x + y
print(z.get_device() == 0) # True
# 手动指定使用GPU 0
d = t.randn(2, 3).cuda(0)
print(d.get_device() == 2) # False
# 指定默认tensor的类型为GPU上的FloatTensor
t.set_default_tensor_type('torch.cuda.FloatTensor')
a = t.ones(2, 3)
a.is_cuda # True
如果服务器具有多个 GPU
,tensor.cuda()
方法会将 tensor
保存到第一块 GPU
上,等价于tensor.cuda(0)
。此时如果想使用第二块 GPU
,需手动指定 tensor.cuda(1)
,而这需要修改大量代码,很是繁琐。这里有两种替代方法:
- 一种是先调用
t.cuda.set_device(1)
指定使用第二块GPU
,后续的.cuda()
都无需更改,切换GPU
只需修改这一行代码。 - 更推荐的方法是设置环境变量
CUDA_VISIBLE_DEVICES
,例如当export CUDA_VISIBLE_DEVICE=1
(下标是从 0 开始,1 代表第二块GPU
),只使用第二块物理GPU
,但在程序中这块GPU
会被看成是第一块逻辑GPU
,因此此时调用tensor.cuda()
会将Tensor
转移至第二块物理GPU
。CUDA_VISIBLE_DEVICES
还可以指定多个GPU
,如export CUDA_VISIBLE_DEVICES=0,2,3
,那么第一、三、四块物理GPU
会被映射成第一、二、三块逻辑GPU
,tensor.cuda(1)
会将Tensor
转移到第三块物理GPU
上。
设置 CUDA_VISIBLE_DEVICES
有两种方法:
- 一种是在命令行中
CUDA_VISIBLE_DEVICES=0,1 python main.py
; - 一种是在程序中
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "2"
如果使用 IPython
或者 Jupyter notebook
,还可以使用 %env CUDA_VISIBLE_DEVICES=1,2
来设置环境变量。
从 0.4 版本开始,PyTorch
新增了 tensor.to(device)
方法,能够实现设备透明,便于实现 CPU/GPU
兼容。
x = torch.randn(1)
if torch.cuda.is_available():
device = torch.device("cuda")
y = torch.ones_like(x, device=device) # directly create a tensor on GPU
x = x.to(device)
z = x + y
print(z)
print(z.to("cpu", torch.double))
输出:
tensor([ 2.0718], device='cuda:0')
tensor([ 2.0718], dtype=torch.float64)
PyTorch
支持分布式 GPU
。分布式是指有多个 GPU
在多台服务器上,而并行一般指的是一台服务器上的多个 GPU
。分布式涉及到了服务器之间的通信,因此比较复杂,PyTorch
封装了相应的接口,可以用几句简单的代码实现分布式训练。分布式对普通用户来说比较遥远,因为搭建一个分布式集群的代价十分大,使用也比较复杂。相比之下一机多卡更加现实。