You called .view() on a tensor, got a RuntimeError: view size is not compatible with input tensor's size and stride, switched to .reshape(), and it just... worked. So what's actually going on — and which one should you use?
The short answer
reshape and view both give you the same numbers under a new shape. The difference is one word: contiguity. view() returns a zero-copy view but requires the tensor to be contiguous — if it isn't, it errors. reshape() returns a view when it can and silently makes a copy when it can't, so it never errors on contiguity. Use reshape() by default; reach for view() only when you specifically need to guarantee shared memory.
Why reshape vs view exists at all
A tensor's data and its shape are two separate things. The data is a flat row of numbers sitting in memory; the shape is just the lens you look at it through. Reshaping changes the lens without touching the numbers — twelve elements can be seen as a 3×4 matrix, a 4×3, a 2×6, or a flat row of 12.
import torch
t = torch.arange(1, 13) # 12 elements: 1..12
print(t.reshape(3, 4))
print(t.reshape(2, 6))
print(t.reshape(12))
The only constraint is that the product of the new shape must equal the total number of elements (numel()). Twelve elements can become (3, 4) because 3 × 4 = 12, but not (3, 5) because 3 × 5 = 15.
When the tensor is contiguous — which it almost always is right after you create it — view and reshape do exactly the same thing: return a zero-cost view of the same memory.
t = torch.arange(12)
a = t.view(3, 4) # view — always free
b = t.reshape(3, 4) # reshape — also free here
print(a.data_ptr() == t.data_ptr()) # True
print(b.data_ptr() == t.data_ptr()) # True
Same data pointer means no copy happened. So far, no difference.
What does "contiguous" mean in PyTorch?
A tensor is contiguous when its elements — read left-to-right along the last dimension, then the next dimension out, and so on — sit in that same order in memory. Most tensors are contiguous from birth. The operations that break contiguity are transpose, permute, and expand: they change the strides without moving the data.
t = torch.arange(6).reshape(2, 3)
print(t.is_contiguous()) # True
print(t.stride()) # (3, 1)
tt = t.T # transpose
print(tt.is_contiguous()) # False
print(tt.stride()) # (1, 3)
After the transpose the strides are reversed. The numbers are still in the same flat row 0 1 2 3 4 5, but reading them in the new dimension order no longer walks through memory in order. That is non-contiguous — and it's exactly the situation where view() breaks.
When view() breaks (the error you googled)
tt = t.T # non-contiguous
tt.view(6) # RuntimeError!
You'll see something like:
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.
PyTorch is telling you the operation isn't safe as a zero-copy view. You have two fixes:
print(tt.reshape(6)) # works — quietly copies
print(tt.contiguous().view(6)) # also works — rearranges memory first, then views
reshape is almost always the right default. Call .contiguous().view(...) only when you have a reason to force the copy explicitly.
Watch out: reshape doesn't guarantee a view
Because reshape returns a view sometimes and a copy other times, you cannot rely on it to share memory. If you need in-place edits to propagate back to the original, use view — and if it errors, that's PyTorch telling you the operation genuinely can't be done without copying.
The -1 shortcut
You can pass -1 for exactly one dimension and PyTorch infers its size from the total element count:
t = torch.arange(24)
print(t.reshape(4, -1).shape) # (4, 6)
print(t.reshape(-1, 3).shape) # (8, 3)
print(t.reshape(2, 3, -1).shape) # (2, 3, 4)
At most one -1 per call. This is everywhere in real code — you know some dimensions and let PyTorch solve for the last.
A worked example: flatten before a linear layer
The single most common reshape in practice is collapsing spatial dimensions before a fully-connected layer, while keeping the batch dimension intact:
batch = torch.randn(32, 3, 28, 28) # N, C, H, W
flat = batch.flatten(1) # flatten from dim 1 onward
print(flat.shape) # torch.Size([32, 2352])
flatten(1) keeps dimension 0 (the batch) and merges the rest into one. Note the trap: flatten() with no argument collapses everything, batch included — inside a model's forward pass, always use flatten(1).
Common mistakes and gotchas
-
Calling
view()aftertranspose/permute. That's the classicRuntimeError. Usereshape()or.contiguous().view(). -
Assuming
reshapeshares memory. Sometimes it copies. If you need a guaranteed view, useview(). -
Using bare
flatten()in a forward pass. It eats the batch dimension. Useflatten(1). -
Wrong element count.
reshape(3, 5)on 12 elements raisesRuntimeError: shape '[3, 5]' is invalid for input of size 12. The product must equalnumel().
Recap
view and reshape both reinterpret the same flat memory under a new shape. view requires contiguity and always returns a true view; reshape returns a view when it can and copies when it must, so it never errors on contiguity. Default to reshape; use view when you specifically need shared memory.
This is one idea from my book PyTorch From Ground Up, which builds everything from tensors upward so nothing stays vague. If it helped, you can grab the free PyTorch tensor cheat-sheet here, run every example from the book in the companion notebooks on GitHub, or find the full paperback on Amazon here or find a digital copy of the book on leanpup here
Top comments (0)