Every operation builds a node
When you operate on a tensor with requires_grad=True, PyTorch creates a graph node that knows two things: what operation produced this tensor (its grad_fn) and which inputs fed into the operation (so backward can walk back to them).
You can inspect this directly:
- Leaf tensors — created directly (e.g.
nn.Parameter, or anytorch.tensor(..., requires_grad=True)). Theirgrad_fnisNoneandis_leafis True. They are where gradients land. - Intermediate tensors — produced by operations. Their
grad_fnpoints to a backward function (AddBackward0,MulBackward0, etc.) and they are not leaves. By default they don't store their own .grad — only leaves do.
Walking the graph backwards
When you call loss.backward(), autograd traces from the loss back through all the connected nodes via the grad_fn links. Each node knows how to compute the local Jacobian (or vector-Jacobian product, technically), and the chain rule composes them into the final gradient at every leaf.
One subtlety: by default the graph is freed immediately after backward to save memory. If you need to call backward again on the same graph, pass retain_graph=True. You usually don't.