Visual walkthrough — Autograd and computational graphs in PyTorch
This page rebuilds the parent result — autograd computing and for — but as a sequence of pictures. Every symbol is earned before it is used. If you have never seen a "gradient" or a "graph node", start here at line one.
We use only two starting numbers, the ones from the parent note:
Step 1 — What is a computational graph, really?
WHAT. Every arithmetic expression is secretly a little assembly line. Write as three simple machines wired together:
- a multiply machine that eats and and outputs ,
- a square machine that eats and outputs ,
- an add machine that eats and and outputs .
WHY. The reason we bother splitting one formula into tiny machines is that each tiny machine has a derivative we already know by heart (multiply, square, add). If we can chain those known derivatives, we never have to differentiate the whole messy formula at once. That chaining is what the graph is for.
PICTURE. Circles are tensors (the numbers flowing), rounded boxes are operations (the machines). Arrows show which number feeds which machine.

Notice has two arrows leaving it: one into the multiply machine, one into the square machine. Hold onto that — it is the whole reason gradients get summed later.
Step 2 — The forward pass: push the numbers through
WHAT. Plug in and let each machine fire in order.
Term by term: is the multiply-box output, is the square-box output, is their sum — the final answer sitting at the end of the line.
WHY. We must run forward first because every machine needs to remember the actual numbers that passed through it. The square machine, for instance, will later need to recall "I saw " to report how sensitive it is. No forward values → no backward gradients.
PICTURE. Same graph, now every arrow is labelled with the real number riding along it.

Step 3 — What does a single machine's derivative mean?
WHAT. Before wiring the whole backward pass, look at ONE machine. Take the square machine, . Its derivative is
The symbol (read "partial dee dee ") answers exactly one question: if I nudge the input up by a tiny amount, how many times bigger is the wobble that comes out at ?
WHY this tool — the derivative and not something else. We want sensitivity. "How much does the output move per unit of input move?" is precisely what a derivative measures. No other tool answers "rate of change at a point". At the answer is : a tiny push on produces a wobble as big on .
PICTURE. The curve with a tiny input step at and the much bigger output step it causes. The slope of the tangent line is the number 4.

Step 4 — The chain rule: multiply the slopes along a path
WHAT. Ask: how sensitive is the final to the square-machine's input, following the path ? The chain rule says: multiply the local slopes along the path.
WHY multiplication? If wiggling makes move times as much, and wiggling makes move time as much, then wiggling makes move times as much. Sensitivities compound — that is multiplication, exactly like gears: two gears with ratios and give a total ratio .
PICTURE. A gear train: input turns a gear, which turns , which turns . The total turn ratio is the product of the two gear ratios written on the edges.

Step 5 — The node has two paths: SUM them
WHAT. But also reaches by a second road, . Its sensitivity along that road is The multivariate chain rule says the total sensitivity of to is the sum over every path:
WHY sum? Wiggling shoves through both roads at once, and the two shoves land at together. Independent contributions to the same quantity add. (This is the "sum over children" rule quoted in the parent's chain-rule callout.)
PICTURE. Two coloured arrows leaving , each carrying its own contribution ( in green, in yellow), meeting and adding at 's gradient slot.

Plugging in :
And has only one road, :
Step 6 — The backward pass as water flowing back
WHAT. PyTorch does not think in "paths". It floods the graph backward. It plants a at (because : is perfectly sensitive to itself), then at every machine it multiplies the incoming backward number by that machine's local slope and pushes the result upstream. Where two streams meet a node, they add.
WHY do it backward instead of forward? Because we want the gradient with respect to many inputs but of one scalar output. Flooding backward from the single output visits every input once. Flooding forward would need a separate sweep per input — far more work. This is the core efficiency of reverse-mode differentiation, the backpropagation algorithm.
PICTURE. The graph with backward arrows in red; each edge labelled by the number travelling back, ending at and .

Step 7 — The degenerate case: what if a machine's slope is zero?
WHAT. Suppose one input never affects the output, like inside the square machine : . Or a ReLU that saw a negative number: its local slope is .
WHY it matters. A local slope of acts like a closed valve: multiply any backward stream by and nothing passes upstream. That input receives no gradient through that path. This is not a bug — it is the machine honestly reporting "I ignore this input, so nudging it changes nothing".
PICTURE. Two backward valves side by side: an open valve (slope , water flows) and a shut valve (slope , water stops), showing a dead path where gradient vanishes.

Step 8 — The other edge case: two backward visits accumulate
WHAT. If you press .backward() twice without clearing, the second flood adds onto the first, because .grad is a running total, not a fresh slate:
So after two backward passes on at : first gives , second gives .
WHY. Mini-batch training wants a sum: the batch gradient is , so accumulating each sample's gradient builds the sum for free. The price: you must call .zero_() between real steps, or old gradients leak into new ones.
PICTURE. A bucket labelled .grad filling up: first pour adds , second pour adds another → level . A tap labelled zero_() empties it.

The one-picture summary
Everything above, compressed: forward numbers in blue, local slopes on the boxes, red backward flow merging at to give and reaching to give .

Recall Feynman retelling — say it back in plain words
I wrote my formula as a little assembly line of tiny machines: multiply, square, add. I pushed my numbers and forward and each machine remembered what it saw. Then I planted a single "1" at the very end — because the answer is perfectly sensitive to itself — and let it flow backward. At each machine I multiplied the incoming backward number by that machine's own slope (multiply's slope is the other input, square's slope is , add's slope is just ). Where 's two roads met, I added their contributions, getting ; had one road and got . If a machine's slope were , that road would be a shut valve and no gradient would pass. And if I run it backward twice without emptying the bucket, the gradients pile up — which is a feature for batches but a trap if I forget to zero them.
Recall
In , why does get gradient but only gets ? ::: reaches by two paths (through and through ), and their contributions sum; reaches by only one path (through ).
What single number do we plant at the output to start the backward pass, and why? ::: We plant , because — the output is perfectly sensitive to itself.
What does a local slope of (e.g. a negative-input ReLU) do to backward flow? ::: It shuts the valve — any gradient multiplied by vanishes, so no gradient passes upstream through that path.
Why must you call .zero_() between training steps? ::: .grad accumulates (adds) each backward pass; without zeroing, old gradients leak into the next step's update.
See also: tensors · backpropagation · torch.nn.Module · gradient checkpointing (trades memory for recomputing this graph).