Reverse execution needs the forward values its rules depend on
Trace a small reverse tape, distinguish graph structure from saved storage, and reason about activation lifetimes, aliases, and memory accounting.
On this page
The question for today
What you’ll understand
- Identify primal values needed by local reverse rules.
- Calculate a small reverse trace with accumulated contributions.
- Distinguish logical saved tensors, physical storage, and references retaining a graph.
A reverse pass runs after the forward operations that produced its output. To evaluate local rules, it needs enough information about those earlier operations: values, metadata, or an equivalent recomputation. A computational tape is one way to organize that information. The important engineering question is not only how many operations exist, but which values remain live, who owns their storage, and when the system can release them.
The reverse rule determines what must be available
For multiplication, input values help compute the reverse contributions; for an exponential, its output can provide the local derivative. Different implementations can save different sufficient residuals while representing the same derivative. An abstract tape records operations and the information needed to reverse them, but a compiled runtime can fuse, simplify, or rematerialize parts of that structure. Do not assume a one-to-one mapping between Python statements, graph nodes, kernels, and saved allocations. The mathematical dependency is stable while the representation and optimization policy vary.
PyTorch 2.9 documents saved tensors as forward intermediates needed by backward rules. It may pack and unpack tensor objects to avoid reference cycles, so object identity is not a reliable API for determining whether underlying storage is shared. A saved tensor can retain a large backing allocation even when its visible slice is small. Conversely, several logical saved views can share one allocation. Inspect storage and lifetime evidence when diagnosing memory rather than summing every printed tensor shape and treating that sum as exact resident bytes.
Graph reachability and saved-data lifetime interact
A graph can remain reachable because an application stores an output, loss, closure, or diagnostic object that references its history. Ordinary backward often releases saved resources when they are no longer needed, subject to graph-retention settings and framework behavior. Retaining a graph can keep memory live longer, but saving more graphs is not the same as recording a differentiable graph of the derivative. These become distinct choices for higher-order work. Avoid keeping unnecessary training outputs across iterations when only a detached scalar statistic is needed, while preserving the intended derivative dependencies.
A memory budget should separate parameters, gradients, optimizer state, saved activations, temporary workspaces, allocator reserves, and application references. The total peak depends on overlapping lifetimes, not the sum of each category's individual peak measured at different times. Saved-tensor hooks and offloading can alter where residuals live and what reconstruction costs later; they must preserve the values required by backward. A lower accelerator-memory number can come with host-memory or transfer costs. Measure both derivative correctness and peak resource usage under the actual execution schedule.
Inspect one residual dependency with a toy computation
For a small trace, write which forward value each reverse rule uses and when that value can be discarded. Compare a repeated backward request, a retained graph, and a deliberately recomputed forward evaluation under the framework's contract. A value that prints correctly now may already have lost the historical value needed for backward after mutation. An error about a freed or modified saved tensor is evidence about a specific lifetime dependency, not a suggestion to turn retention on everywhere. Identify the intended schedule before choosing a fix.
The network lab gives one inspectable sigmoid, not a reverse runtime. Reset, set BOTH input x=1 and weight w=2, predict z=wx−0.5=1.5 and output about 0.8176, then Play or Step. On paper, its input derivative is wσ(z)(1−σ(z))≈0.2983. Reset and re-set BOTH x=0.5 and w=2 to inspect a different forward value. Edits retain t; playback traces the fixed graph without computing a backward tape or saving training activations. Explain which selected primal values a real local rule would require rather than treating the animation as memory evidence.
Work it through
Reverse a sine-and-square trace
For ℓ=sin(xy)+x², evaluate at x=2 and y=0.5. Seed the scalar output with one and use the saved forward product r=1.
The sine path sends cotangent cos(1)≈0.540302 to r. Multiplication sends y cos(1)≈0.270151 to x and x cos(1)≈1.080605 to y.
The separate square path contributes 2x=4 to x. Accumulate it with the multiplication contribution rather than overwriting either path.
The final input gradient is approximately (4.270151,1.080605). Using another evaluation's product or forgetting the square contribution would produce a different derivative.
The gradient depends on compatible forward residuals and correct accumulation through the reused input x.
The living notebook
Explore the model
Interactive lab · network
Identify a local rule's primal dependencies
For EACH run Reset, set BOTH x and w, predict, then Play or Step. Compare (x,w)=(1,2) and (0.5,2) in σ(wx−0.5). Compute wσ(1−σ) on paper. Edits retain t; this single-unit graph has no backward tape, saved-tensor storage, training, or memory profiler.
- Product · wx
- 1.2
- Logit · wx − 0.5
- 0.7
- Sigmoid output
- 0.6682
One inspectable sigmoid unit, σ(wx − 0.5). Playback traces the computation graph. There is no training, attention mechanism or hidden deep network. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forReverse rules require values from the corresponding forward evaluation; a computation picture alone does not establish their storage lifetime.
Pause & reason
Check your understanding
Your turn
Try it yourself
Three saved views share one 1,000,000-element allocation of four-byte values. Another distinct saved allocation contains 250,000 such elements. Estimate distinct payload bytes and name two excluded costs.
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Count shared storage once, then add the independent allocation.
Compare with the solution
Payload is (1,000,000+250,000)×4=5,000,000 bytes. Metadata, alignment, allocator reserves, transfer staging, and temporary workspaces are excluded. Counting each shared view as another full allocation would incorrectly multiply the first payload.
Keep the idea
Take it with you
- Local reverse rules determine required residual information, not source-line count.
- Logical tensor views, retained allocations, and graph references have different memory meanings.
- Correct lifetime management preserves compatible forward values while controlling peak resources.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.