pytorch笔记:contiguous &tensor 存储知识

Posted UQI-LIUWJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pytorch笔记:contiguous &tensor 存储知识相关的知识,希望对你有一定的参考价值。

1 contiguous

        contiguous直观的解释是Tensor底层一维数组元素的存储顺序与Tensor按行优先一维展开的元素顺序是否一致

1.1 tensor的存储

        Tensor多维数组底层实现是使用一块连续内存的1维数组,Tensor在元信息里保存了多维数组的形状。

        在访问元素时,通过多维度索引转化成1维数组相对于数组起始位置的偏移量即可找到对应的数据。

        某些Tensor操作(如transpose、permute、narrow、expand)与原Tensor是共享内存中的数据,不会改变底层数组的存储。但原来在语义上相邻、内存里也相邻的元素在执行这样的操作后,在语义上相邻,但在内存不相邻,即不连续了(is not contiguous)。

        如果想要tensor继续变得连续,则需要使用contiguous方法。

  • 如果Tensor不是连续的,则会重新开辟一块内存空间保证数据是在内存中是连续的
  • 如果Tensor是连续的,则contiguous无操作

1.2 行优先与列优先

        C/C++中使用的是行优先方式(row major),Matlab、Fortran使用的是列优先方式(column major),PyTorch中Tensor底层实现是C,也是使用行优先顺序。

     

  比如下面这个Tensor:

>>> t = torch.arange(12).reshape(3,4)
>>> t
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])

二维数组t长这个样子

        但是数组 t 在内存中实际以一维数组形式存储(可以通过 flatten 方法查看 t 的一维展开形式):

>>> t.flatten()
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

 

 如果我们将其转置了,有:

>>>t.t()
tensor([[ 0,  4,  8],
        [ 1,  5,  9],
        [ 2,  6, 10],
        [ 3,  7, 11]])

 

形式上数组转置了,但是存储形式没变,还是从0~11的一维数组

 

1.3 pytorch判断是否 连续

import torch
t = torch.arange(12).reshape(3,4)
t.is_contiguous()
#True

tt=t.t()
tt.is_contiguous()
#False

 2 为什么要连续?

2.1 torch.view等方法操作需要连续的Tensor。

        transpose、permute 操作没有修改底层一维数组,而是新建了一份Tensor元信息,并在新的元信息中重新指定 stride。

        而torch.view 方法约定了不修改数组本身,只是使用新的形状查看数据。

        如果我们在 transpose、permute 操作后执行 view,Pytorch 会抛出以下错误:

>>> tt=t.t()
>>> t.view(12)
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
>>> tt.view(12)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.

        view只是用最底层数据的存储方式来进行形状切换,它看不到transpose、permute这些操作指定的stride。

   

  所以如果tt.view()不报错的话,它的输出也不是

[ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11]

        而是

[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11]

 这显然是不满足预期的

所以需要先用contiguous方法,重新开辟一块内存

>>> t2=tt.contiguous()
>>> t2.view(12)
tensor([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])

2.2 为什么view方法不默认调用contiguous?

2.2.1 历史原因

        因为历史上view方法已经约定了共享底层数据内存,返回的Tensor底层数据不会使用新的内存,如果在view中调用了contiguous方法,则可能在返回Tensor底层数据中使用了新的内存,这样打破了之前的约定,破坏了对之前的代码兼容性。

        为了解决用户使用便捷性问题,PyTorch在0.4版本以后提供了reshape方法,实现了类似于 tensor.contigous().view(*args)的功能,如果不关心底层数据是否使用了新的内存,则使用reshape方法更方便

2.2.2 性能考虑

        连续的Tensor,语义上相邻的元素,在内存中也是连续的,访问相邻元素是矩阵运算中经常用到的操作,语义和内存顺序的一致性是缓存友好(cache friendly)的。

        连续内存布局减少了CPU对对内存的请求次数,相当于空间换时间。

        

        

参考文献:python - What is the difference between contiguous and non-contiguous arrays? - Stack Overflow

PyTorch中的contiguous - 知乎 (zhihu.com)

以上是关于pytorch笔记:contiguous &tensor 存储知识的主要内容,如果未能解决你的问题,请参考以下文章

Pytorch中contiguous()函数理解

reshape() 是不是与 contiguous().view() 相同?

PyTorch性能优化的学习笔记

PyTorch性能优化的学习笔记

pytorch 笔记: 协同过滤user & item bias 实现

pytorch 中的viewreshapepermutetranspose