据我所知,每当某个函数或模块需要连续张量时,都需要显式调用tensor.contiguous()
。否则,您会得到以下异常:
RuntimeError: invalid argument 1: input is not contiguous at .../src/torch/lib/TH/generic/THTensor.c:231
(例如通过。(
哪些功能或模块需要连续输入这有记录吗?
或者用不同的措辞,在哪些情况下需要调用contiguous
?
例如Conv1d
,它是否需要连续输入?文件中没有提到这一点。当文档没有提到这一点时,这总是意味着它不需要连续输入?
(我记得在Theano中,任何得到非连续输入的操作,都需要它是连续的,只会自动转换它。(
通过source_code进一步挖掘后,似乎只有view
函数在传递非连续输入时显式导致异常。
可以预期任何使用张量视图的操作具有在非连续输入时失败的可能性。事实上,似乎大多数或所有这些功能都是:
(a.(在支持非连续块的情况下实现(见下面的示例(,即张量迭代器可以处理指向内存中不同数据块的多个指针,这可能会以牺牲性能为代价,或者
(b.(对.contiguous()
的调用封装该操作(这里针对torch.tensor.diagflat()
示出了一个这样的示例(。CCD_ 7本质上是CCD_ 9的CCD_。
从扩展的角度来看,view
相对于reshape
的主要好处似乎是,当张量出乎意料地不连续时,会出现显式异常,而代码则会以牺牲性能为代价默默地处理这种差异。
这一结论基于:
- 使用非连续输入测试所有张量视图操作
- 感兴趣的其他非张量视图函数的源代码分析(例如Conv1D,它包括在所有非平凡输入情况下对
contiguous
的必要调用( - pytorch的设计哲学作为一种简单、有时缓慢、易于使用的语言的推论
- Pytorch讨论上的交叉张贴
- 对涉及非连续错误的网络报告错误进行了广泛的审查,所有这些错误都围绕着对
view
的有问题的调用
我没有全面测试所有pytorch函数,因为有数千个。
(a.(示例:
import torch
import numpy
import time
# allocation
start = time.time()
test = torch.rand([10000,1000,100])
torch.cuda.synchronize()
end = time.time()
print("Allocation took {} sec. Data is at address {}. Contiguous:
{}".format(end -
start,test.storage().data_ptr(),test.is_contiguous()))
# view of a contiguous tensor
start = time.time()
test.view(-1)
torch.cuda.synchronize()
end = time.time()
print("view() took {} sec. Data is at address {}. Contiguous:
{}".format(end -
start,test.storage().data_ptr(),test.is_contiguous()))
# diagonal() on a contiguous tensor
start = time.time()
test.diagonal()
torch.cuda.synchronize()
end = time.time()
print("diagonal() took {} sec. Data is at address {}. Contiguous:
{}".format(end -
start,test.storage().data_ptr(),test.is_contiguous()))
# Diagonal and a few tensor view ops on a non-contiguous tensor
test = test[::2,::2,::2] # indexing is a Tensor View op
resulting in a non-contiguous output
print(test.is_contiguous()) # False
start = time.time()
test = test.unsqueeze(-1).expand([test.shape[0],test.shape[1],test.shape[2],100]).diagonal()
torch.cuda.synchronize()
end = time.time()
print("non-contiguous tensor ops() took {} sec. Data is at
address {}. Contiguous: {}".format(end -
start,test.storage().data_ptr(),test.is_contiguous()))
# reshape, which requires a tensor copy operation to new memory
start = time.time()
test = test.reshape(-1) + 1.0
torch.cuda.synchronize()
end = time.time()
print("reshape() took {} sec. Data is at address {}. Contiguous: {}".format(end - start,test.storage().data_ptr(),test.is_contiguous()))
输出以下内容:
Allocation took 4.269254922866821 sec. Data is at address 139863636672576. Contiguous: True
view() took 0.0002810955047607422 sec. Data is at address 139863636672576. Contiguous: True
diagonal() took 6.532669067382812e-05 sec. Data is at address 139863636672576. Contiguous: True
False
non-contiguous tensor ops() took 0.00011277198791503906 sec. Data is at address 139863636672576. Contiguous: False
reshape() took 0.13828253746032715 sec. Data is at address 94781254337664. Contiguous: True
块4中的一些张量视图操作是在非连续输入张量上执行的。该操作运行无误,将数据保持在相同的存储器地址中,并且运行速度相对快于需要复制到新存储器地址的操作(例如块5中的reshape
(。因此,这些操作似乎是以一种处理非连续输入而不需要数据副本的方式实现的。
来自pytorch文档:contiqueous((→张量。返回包含与自身张量相同数据的连续张量。如果自张量是连续的,则此函数返回自张量。
我不认为有一个完整的列表。这取决于如何实现张量处理函数。
如果你看一下关于编写C++和CUDA扩展的教程,你会发现一个典型的pytorch CUDA操作看起来像:
带有torch::Tensor
参数的C++接口。这个类提供了访问/操作张量数据的API
float*
参数的CUDA内核。这些指针直接指向存储张量数据的存储器显然,用指针处理张量中的数据可能比处理张量类的API效率高得多。但最好处理具有连续内存布局(或者至少是常规布局(的指针。
原则上,我相信,如果给定足够的内存布局信息,即使没有连续数据,也可以用指针操作数据。但是,您必须考虑各种布局,代码可能会更加乏味。
Facebook可能有一些技巧可以让一些内置操作在不连续的数据上工作(我对此了解不多(,但大多数自定义扩展模块都要求输入是连续的。