Tensors
PyTorch tensors: creation, dtype, device, autograd, in-place vs out-of-place, and the GPU patterns that matter.
PyTorch — tensors
EXAMPLE
import torch
# ===== Creation =====
a = torch.tensor([1.0, 2.0, 3.0]) # 1D float32
b = torch.zeros(3, 4)
o = torch.ones(2, 2, dtype=torch.float64)
r = torch.arange(0, 10, 2)
l = torch.linspace(0, 1, 5)
n = torch.randn(3, 3) # standard normal
i = torch.randint(0, 10, (3, 3))
# From NumPy (shares memory!):
import numpy as np
nd = np.arange(6, dtype=np.float32).reshape(2, 3)
t = torch.from_numpy(nd)
t[0, 0] = 99
# nd[0, 0] is now also 99 — they share storage.
# Explicit copy:
t2 = t.clone()
# ===== Five attributes you always check =====
print(t.shape, t.dtype, t.device, t.requires_grad, t.layout)
# ===== Reshape =====
v = b.view(-1) # view (must be contiguous)
r = b.reshape(-1) # reshape (copies if needed)
tr = b.transpose(0, 1) # swap axes 0 and 1
sq = b.unsqueeze(0).squeeze() # add/remove size-1 dims
# ===== Indexing =====
b[0, 1] = 5
col = b[:, 1] # view
row = b[1, :] # view
mask = b > 0
flat = b[mask] # 1D copy
# ===== Math (broadcasting like NumPy) =====
x = torch.tensor([[1., 2., 3.], [4., 5., 6.]])
y = torch.tensor([10., 20., 30.])
print(x + y)
print(x @ x.T) # matmul
print(x.sum(dim=0), x.mean(dim=1))
# ===== In-place ops (suffix _) =====
b.add_(1) # in-place
# Avoid in-place ops on tensors that require grad and have already been used.
# ===== Device placement =====
device = 'cuda' if torch.cuda.is_available() else 'cpu'
x = x.to(device)
y = y.to(device, non_blocking=True) # async copy when pinned
# Multi-GPU:
# x = x.to('cuda:1')
# ===== Autograd =====
w = torch.randn(3, 2, requires_grad=True)
b = torch.zeros(2, requires_grad=True)
X = torch.randn(10, 3); target = torch.randn(10, 2)
pred = X @ w + b
loss = ((pred - target) ** 2).mean()
loss.backward() # populates w.grad and b.grad
# Update + zero out:
with torch.no_grad():
w -= 0.01 * w.grad
b -= 0.01 * b.grad
w.grad.zero_(); b.grad.zero_()
# ===== Detaching =====
proba = pred.detach() # break autograd link
# Used in eval / logging where you don't want gradients to flow.
# ===== Mixed precision (autocast) =====
from torch.amp import autocast
with autocast('cuda', dtype=torch.float16):
out = X @ w + b
# ===== Conversion =====
back = pred.detach().cpu().numpy() # detach + move + convert
# ===== Patterns to internalise =====
# - .shape and .dtype before every operation in new code
# - .to(device) + pin_memory + non_blocking is the GPU perf trinity
# - In-place ops save memory; avoid them on tensors that require grad
# - Use torch.no_grad() for eval and parameter updates
# - .detach() before .numpy() or any side-effect path
# ===== Pitfalls =====
# - Mixing CPU and GPU tensors -> 'expected all tensors to be on the same device'
# - Forgetting to zero gradients -> they accumulate across batches
# - torch.from_numpy shares storage; mutating one mutates the other
# - .view on non-contiguous tensors fails; use .reshape() to be safe
# - autocast for fp16 without a GradScaler in training -> underflow ruins gradients
Why it matters
PyTorch tensors are NumPy + GPU + autograd. Shape, dtype, device, requires_grad are the four things to know about any tensor at any time. The mental model is "data + flag + gradients" — once that clicks, the rest of PyTorch falls into place.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import torch x = torch.tensor([[1., 2.], [3., 4.]]) print(x.shape, x.dtype, x.device) y = torch.zeros(2, 3) z = torch.randn(3, 3)Try it Yourself »
Exercise
Create a random tensor.
x = torch.
(2, 3)
Five letters.
Discussion
Loading…