Worked examples — Autograd and computational graphs in PyTorch
This page is the practice deep-dive for Autograd and computational graphs in PyTorch. The parent note built the machine; here we run it on every kind of input it can face. Before any code, we map out the full space of cases so nothing surprises you later.
If a word here feels unfamiliar (gradient, chain rule, .backward()), the parent note defines it — this page assumes you have read it and now want to feel the mechanics through examples.
The scenario matrix
Autograd is a small idea (record operations, replay them backward) but it has many corners. A gradient can be positive, negative, or exactly zero; a graph can be used once or many times; a branch can be alive or cut off. The table below lists every corner. Each later example is tagged with the cell it lands in.
| # | Case class | What is special about it | Covered by |
|---|---|---|---|
| A | Positive gradient | derivative : output rises when input rises | Ex 1 |
| B | Negative gradient | derivative : output falls when input rises | Ex 2 |
| C | Zero gradient (dead branch) | derivative at the operating point (ReLU on a negative, saturated point) | Ex 3 |
| D | Multiple paths to one node | a variable feeds two operations — gradients must sum | Ex 4 |
| E | Gradient accumulation across calls | .backward() twice without zeroing — the .grad adds up |
Ex 5 |
| F | Detached / stopped gradient | .detach() cuts the graph — no gradient flows past it |
Ex 6 |
| G | Non-scalar output | .backward() on a vector needs a seed vector (Jacobian-vector product) |
Ex 7 |
| H | Real-world word problem | linear layer + loss, compute weight gradients | Ex 8 |
| I | Exam twist / degenerate | requires_grad=False leaf, or in-place error |
Ex 9 |
Every numeric answer below is machine-checked in the verify block.
Example 1 — Positive gradient (Cell A)
Steps.
-
Forward: . Why this step? We must run the forward pass so PyTorch records the graph (a
PowBackwardand anAddBackwardnode). -
Hand-derivative: . Why this step? Power rule on gives ; the linear term gives . Adds because derivative of a sum is the sum of derivatives.
-
Plug : . Why this step? Autograd evaluates the derivative at the current point, not symbolically.
x = torch.tensor([2.0], requires_grad=True)
z = x**2 + 3*x
z.backward()
print(x.grad) # tensor([7.])Verify: , so increases with — matches the intuition that both and rise for . ✓
Example 2 — Negative gradient (Cell B)
Steps.
-
Forward: . Why? Record the graph.
-
Derivative: . Why? ; by the power rule, with the minus sign carried through.
-
Plug : . Still positive here. Try : — negative. Why the second point? To actually hit Cell B we need a point where the slope is negative; delivers it.
x = torch.tensor([2.0], requires_grad=True)
z = 4*x - x**3
z.backward()
print(x.grad) # tensor([-8.])Verify: at , increasing shrinks (the term dominates). Sign confirms Cell B. ✓
Example 3 — Zero gradient / dead branch (Cell C)

Steps.
-
Forward: . Why? ReLU clamps negatives to zero — look at the red flat segment on the left of the figure.
-
Slope of ReLU: it is for and for . Why? On the left the graph is a flat horizontal line — flat means zero slope. On the right it is the line — slope .
-
At we sit on the flat part, so . Why this matters? A zero gradient means no learning signal flows back through this unit — the "dying ReLU" problem.
x = torch.tensor([-1.5], requires_grad=True)
y = torch.relu(x)
y.backward()
print(x.grad) # tensor([0.])Verify: the branch is dead — moving a tiny bit still gives , so the change per unit input is . ✓
Example 4 — One variable, multiple paths (Cell D)

Steps.
-
Draw the graph (figure): splits into a square path () and a linear path (), then an
Addmerges them. Why? The red node has two outgoing edges — this is exactly the multi-path case. -
Path 1 gradient: . Path 2 gradient: . Why separately? The multivariate chain rule says each path from to contributes independently.
-
Sum the paths: . Why sum, not multiply? The paths run in parallel into the same output. Parallel contributions add (they'd multiply only if they were in series, one after the other).
x = torch.tensor([3.0], requires_grad=True)
z = x*x + x
z.backward()
print(x.grad) # tensor([7.])Verify: direct derivative matches the sum-of-paths answer. ✓
Example 5 — Accumulation across two backward calls (Cell E)
Steps.
-
First backward:
x.grad. Why?.gradstarts empty, so it just stores . -
Second backward (graph retained): PyTorch adds to the existing
.grad. Why add? By default.backward()accumulates so that mini-batch gradients sum automatically (parent note, Step 3). -
Result: .
x = torch.tensor([2.0], requires_grad=True)
y = x**2
y.backward(retain_graph=True) # x.grad = 4
y.backward() # x.grad = 4 + 4 = 8
print(x.grad) # tensor([8.])Verify: . ✓
Example 6 — Detaching to stop gradient flow (Cell F)

Steps.
-
Compute the value: , (detach copies the value, drops the history). Why?
.detach()returns a tensor equal in value but with nograd_fn— see the red scissors cutting the edge in the figure. -
Now where is treated as a constant . Why? Autograd cannot see through back to ; that path is severed.
-
So (the visible only), not . Why? Only the un-detached occurrence of contributes.
x = torch.tensor([2.0], requires_grad=True)
a = x**2
b = a.detach()
z = b * x
z.backward()
print(x.grad) # tensor([4.])Verify: treating as constant, . Detach removed the that the path would have added (). ✓
Example 7 — Non-scalar output needs a seed (Cell G)
Steps.
-
.backward()on a scalar means "start with ". A vector has no single output to start from. Why the error? Autograd needs to know how much each output matters — a weight per component. -
Supply that weight vector . Autograd computes the Jacobian-vector product . Why this tool (Jacobian-vector product) and not a full Jacobian? Storing the whole Jacobian is wasteful; deep learning only ever needs (that is exactly what the chain rule multiplies), so autograd computes it directly.
-
Each component: , weighted by : .
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x**2
y.backward(torch.tensor([1.0, 1.0, 1.0]))
print(x.grad) # tensor([2., 4., 6.])Verify: each entry equals : . Using is the same as calling
y.sum().backward(). ✓
Example 8 — Real-world word problem: a linear layer (Cell H)
Steps.
-
Forward: . Why? Standard weighted sum — the forward pass of one neuron.
-
Since , . Why? Identity loss; its derivative is .
-
Chain to each weight: . So , , . Why input? In , differentiating w.r.t. leaves ; the bias has coefficient .
x = torch.tensor([1.0, 2.0])
w = torch.tensor([0.5, -0.3], requires_grad=True)
b = torch.tensor([0.1], requires_grad=True)
z = (w * x).sum() + b
z.backward()
print(w.grad) # tensor([1., 2.])
print(b.grad) # tensor([1.])This is the atom of 3.2.04-Backpropagation-algorithm and of every layer in 3.4.01-Building-neural-networks-with-torch.nn.Module.
Verify: , , independent of the (zero) value of . ✓
Example 9 — Exam twist: a non-tracked leaf (Cell I)
Steps.
-
Only tensors with
requires_grad=Trueare tracked. Why? Autograd records operations on tracked leaves; untracked ones are treated as constants (efficiency — you rarely need gradients w.r.t. fixed data). -
, so
w.grad = 2. Why? In , differentiating w.r.t. leaves the constant . -
x.grad = None, because was never marked for tracking. Why not zero?Nonemeans "no gradient buffer was ever created", which is different from a computed value of .
x = torch.tensor([2.0], requires_grad=False)
w = torch.tensor([3.0], requires_grad=True)
z = w * x
z.backward()
print(w.grad) # tensor([2.])
print(x.grad) # NoneVerify: ; is
None. ✓
Recall
Recall Why do parallel paths from one node
add their gradients? Because they are separate routes into the same output; the multivariate chain rule sums contributions along parallel paths (series paths would multiply). ::: See Example 4.
Recall What is the difference between
x.grad = None and x.grad = 0?
None means the tensor was never tracked (requires_grad=False); 0 means it was tracked but the slope at that point is zero (e.g. ReLU on a negative). ::: Examples 3 and 9.
Recall Why does
y.backward() fail for a vector y?
A vector has no single scalar to seed ; you must pass a weight vector so autograd can compute . ::: Example 7.
Connections: gradient flow feeds 3.5.02-Gradient-descent-variants; detaching is central to 4.2.03-Batch-normalization running stats and 5.3.01-Transfer-learning frozen backbones; retaining/rebuilding graphs relates to 6.4.02-Gradient-checkpointing. Tensor basics: 3.301-Introduction-to-PyTorch-tensors-and-operations.