| .. currentmodule:: torch |
| |
| .. _tensor-view-doc: |
| |
| Tensor Views |
| ============= |
| |
| PyTorch allows a tensor to be a ``View`` of an existing tensor. View tensor shares the same underlying data |
| with its base tensor. Supporting ``View`` avoids explicit data copy, thus allows us to do fast and memory efficient |
| reshaping, slicing and element-wise operations. |
| |
| For example, to get a view of an existing tensor ``t``, you can call ``t.view(...)``. |
| |
| :: |
| |
| >>> t = torch.rand(4, 4) |
| >>> b = t.view(2, 8) |
| >>> t.storage().data_ptr() == b.storage().data_ptr() # `t` and `b` share the same underlying data. |
| True |
| # Modifying view tensor changes base tensor as well. |
| >>> b[0][0] = 3.14 |
| >>> t[0][0] |
| tensor(3.14) |
| |
| Since views share underlying data with its base tensor, if you edit the data |
| in the view, it will be reflected in the base tensor as well. |
| |
| Typically a PyTorch op returns a new tensor as output, e.g. :meth:`~torch.Tensor.add`. |
| But in case of view ops, outputs are views of input tensors to avoid unnecessary data copy. |
| No data movement occurs when creating a view, view tensor just changes the way |
| it interprets the same data. Taking a view of contiguous tensor could potentially produce a non-contiguous tensor. |
| Users should pay additional attention as contiguity might have implicit performance impact. |
| :meth:`~torch.Tensor.transpose` is a common example. |
| |
| :: |
| |
| >>> base = torch.tensor([[0, 1],[2, 3]]) |
| >>> base.is_contiguous() |
| True |
| >>> t = base.transpose(0, 1) # `t` is a view of `base`. No data movement happened here. |
| # View tensors might be non-contiguous. |
| >>> t.is_contiguous() |
| False |
| # To get a contiguous tensor, call `.contiguous()` to enforce |
| # copying data when `t` is not contiguous. |
| >>> c = t.contiguous() |
| |
| For reference, here’s a full list of view ops in PyTorch: |
| |
| - Basic slicing and indexing op, e.g. ``tensor[0, 2:, 1:7:2]`` returns a view of base ``tensor``, see note below. |
| - :meth:`~torch.Tensor.adjoint` |
| - :meth:`~torch.Tensor.as_strided` |
| - :meth:`~torch.Tensor.detach` |
| - :meth:`~torch.Tensor.diagonal` |
| - :meth:`~torch.Tensor.expand` |
| - :meth:`~torch.Tensor.expand_as` |
| - :meth:`~torch.Tensor.movedim` |
| - :meth:`~torch.Tensor.narrow` |
| - :meth:`~torch.Tensor.permute` |
| - :meth:`~torch.Tensor.select` |
| - :meth:`~torch.Tensor.squeeze` |
| - :meth:`~torch.Tensor.transpose` |
| - :meth:`~torch.Tensor.t` |
| - :attr:`~torch.Tensor.T` |
| - :attr:`~torch.Tensor.H` |
| - :attr:`~torch.Tensor.mT` |
| - :attr:`~torch.Tensor.mH` |
| - :attr:`~torch.Tensor.real` |
| - :attr:`~torch.Tensor.imag` |
| - :meth:`~torch.Tensor.view_as_real` |
| - :meth:`~torch.Tensor.unflatten` |
| - :meth:`~torch.Tensor.unfold` |
| - :meth:`~torch.Tensor.unsqueeze` |
| - :meth:`~torch.Tensor.view` |
| - :meth:`~torch.Tensor.view_as` |
| - :meth:`~torch.Tensor.unbind` |
| - :meth:`~torch.Tensor.split` |
| - :meth:`~torch.Tensor.hsplit` |
| - :meth:`~torch.Tensor.vsplit` |
| - :meth:`~torch.Tensor.tensor_split` |
| - :meth:`~torch.Tensor.split_with_sizes` |
| - :meth:`~torch.Tensor.swapaxes` |
| - :meth:`~torch.Tensor.swapdims` |
| - :meth:`~torch.Tensor.chunk` |
| - :meth:`~torch.Tensor.indices` (sparse tensor only) |
| - :meth:`~torch.Tensor.values` (sparse tensor only) |
| |
| .. note:: |
| When accessing the contents of a tensor via indexing, PyTorch follows Numpy behaviors |
| that basic indexing returns views, while advanced indexing returns a copy. |
| Assignment via either basic or advanced indexing is in-place. See more examples in |
| `Numpy indexing documentation <https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html>`_. |
| |
| It's also worth mentioning a few ops with special behaviors: |
| |
| - :meth:`~torch.Tensor.reshape`, :meth:`~torch.Tensor.reshape_as` and :meth:`~torch.Tensor.flatten` can return either a view or new tensor, user code shouldn't rely on whether it's view or not. |
| - :meth:`~torch.Tensor.contiguous` returns **itself** if input tensor is already contiguous, otherwise it returns a new contiguous tensor by copying data. |
| |
| For a more detailed walk-through of PyTorch internal implementation, |
| please refer to `ezyang's blogpost about PyTorch Internals <http://blog.ezyang.com/2019/05/pytorch-internals/>`_. |