东阳的学习记录,坚持就是胜利!
文章目录
自动求导机制
pytorch中的每个变量都有两个属性:requires_grad和volatile。
1. requires_grad
只有所有输入都不需要梯度,输出才不需要。如果其中所有的变量都不需要梯度进行,后向计算不会在子图中执行。
>>> x = Variable(torch.randn(5, 5))
>>> y = Variable(torch.randn(5, 5))
>>> z = Variable(torch.randn(5, 5), requires_grad=True)
>>> a = x + y
>>> a.requires_grad
False
>>> b = a + z
>>> b.requires_grad
True
2. volatile
volatile=True是Variable的另一个重要的标识,它能够将所有依赖它的节点全部设为volatile=True,其优先级比requires_grad=True高。因而volatile=True的节点不会求导,即使requires_grad=True,也不会进行反向传播,对于不需要反向传播的情景(inference,测试推断),该参数可以实现一定速度的提升,并节省一半的显存,因为其不需要保存梯度。(在0.4版本中已经移除,并可以用with torch.no_grad()代替, 或者相对应的装饰器)
x = torch.tensor([1], requires_grad=True)
with torch.no_grad():
... y = x * 2
y.requires_grad
False
@torch.no_grad()
def doubler(x):
return x * 2
z = doubler(x)
z.requires_grad
False
自动求导如何编码历史信息
pytorch维护一个有向无环图,每个变量都有一个.creator属性,它指向把它作为输出的函数。这是一个由Function对象作为节点组成的有向无环图(DAG)的入口点,它们之间的引用就是图的边。每次执行一个操作时,一个表示它的新Function就被实例化,它的forward()方法被调用,并且它输出的Variable的创建者被设置为这个Function。然后,通过跟踪从任何变量到叶节点的路径,可以重建创建数据的操作序列,并自动计算梯度。
inplace操作
inplace操作会对计算图进行重写,所以不推荐使用,除非您在内存压力很大的情况下,否则您可能永远不需要使用它们。
只有那些在反向传播过程中不需要使用到的tensor才能做inplace操作,否则不能。
inplace检查
每个变量保留有version counter,它每次都会递增,当在任何操作中被使用时。当Function保存任何用于后向的tensor时,还会保存其包含变量的version counter。一旦访问self.saved_tensors,它将被检查,如果它大于保存的值,则会引起错误。
cuda语义
torch.cuda会记录当前选择的GPU,并且分配的所有CUDA张量将在上面创建。可以使用torch.cuda.device上下文管理器更改所选设备。
但是,一旦张量被分配,您可以直接对其进行操作,而不考虑所选择的设备,结果将始终放在与张量相同的设备上。
默认情况下,不支持跨GPU操作,唯一的例外是copy_()。 除非启用对等存储器访问,否则对分布不同设备上的张量任何启动操作的尝试都将会引发错误。
x = torch.cuda.FloatTensor(1)
# x.get_device() == 0
y = torch.FloatTensor(1).cuda()
# y.get_device() == 0
with torch.cuda.device(1):
# allocates a tensor on GPU 1
a = torch.cuda.FloatTensor(1)
# transfers a tensor from CPU to GPU 1
b = torch.FloatTensor(1).cuda()
# a.get_device() == b.get_device() == 1
c = a + b
# c.get_device() == 1
z = x + y
# z.get_device() == 0
# even within a context, you can give a GPU id to the .cuda call
d = torch.randn(2).cuda(2)
# d.get_device() == 2
最佳实践
使用固定的内存缓冲区
使用固定的内存缓冲区,可以加快主机到GPU的复制速度。
当副本来自固定(页锁)内存时,主机到GPU的复制速度要快很多。CPU张量和存储开放了一个pin_memory()方法,它返回该对象的副本,而它的数据放在固定区域中。
另外,一旦固定了张量或存储,就可以使用异步的GPU副本。只需传递一个额外的async=True参数到cuda()的调用。这可以用于将数据传输与计算重叠。
通过将pin_memory=True传递给其构造函数,可以使DataLoader将batch返回到固定内存中。
使用 nn.DataParallel 替代 multiprocessing
大多数涉及批量输入和多个GPU的情况应默认使用DataParallel来使用多个GPU。尽管有GIL的存在,单个python进程也可能使多个GPU饱和。
从0.1.9版本开始,大量的GPU(8+)可能未被充分利用。然而,这是一个已知的问题,也正在积极开发。和往常一样,测试你的用例吧。
调用multiprocessing来利用CUDA模型存在重要的注意事项;使用具有多处理功能的CUDA模型有重要的注意事项; 除非就是需要谨慎地满足数据处理需求,否则您的程序很可能会出现错误或未定义的行为。
拓展pytorch(见文档)
使用多进程
torch.multiprocessing是Pythonmultiprocessing的替代品。它支持完全相同的操作,但扩展了它以便通过multiprocessing.Queue发送的所有张量将其数据移动到共享内存中,并且只会向其他进程发送一个句柄。
-
当Variable发送到另一个进程时,Variable.data和Variable.grad.data都将被共享。
-
这允许实现各种训练方法,如Hogwild,A3C或需要异步操作的任何其他方法。
共享CUDA张量
仅在Python 3中使用spawn或forkserver启动方法才支持在进程之间共享CUDA张量。Python 2中的multiprocessing只能使用fork创建子进程,并且不被CUDA运行时所支持。
注意事项
避免死锁
如果您发现死锁情况,请尝试使用multiprocessing.queues.SimpleQueue,这不会使用任何其他线程。
异步训练
使用torch.multiprocessing,可以异步地训练模型,参数可以一直共享,也可以定期同步。在第一种情况下,我们建议发送整个模型对象,而在后者中,我们建议只发送state_dict()。
我们建议使用multiprocessing.Queue来在进程之间传递各种PyTorch对象。例如, 当使用fork启动方法时,可能会继承共享内存中的张量和存储器,但这是非常容易出错的,应谨慎使用,而且只能由高级用户使用。队列虽然有时是一个较不优雅的解决方案,但基本上能在所有情况下正常工作。
import torch.multiprocessing as mp
from model import MyModel
def train(model):
# Construct data_loader, optimizer, etc.
for data, labels in data_loader:
optimizer.zero_grad()
loss_fn(model(data), labels).backward()
optimizer.step() # This will update the shared parameters
if __name__ == '__main__':
num_processes = 4
model = MyModel()
# NOTE: this is required for the ``fork`` method to work
model.share_memory()
processes = []
for rank in range(num_processes):
p = mp.Process(target=train, args=(model,))
p.start()
processes.append(p)
for p in processes:
p.join()
如何保存模型
- 只保存和加载模型参数(推荐使用)
# 1. 只保存和加载模型参数
torch.save(the_model.state_dict(), PATH)
the_model = TheModelClass(*args, **kwargs)
the_model.load_state_dict(torch.load(PATH))
- 保存和加载整个模型:
torch.save(the_model, PATH)
the_model = torch.load(PATH)
第二种方式在不同环境中使用时,可能会出错,不推荐使用