The application owns the model, data, outputs, and loop. The tape records one evaluation of the model, computes gradients, and then an optimizer changes the parameter values.
This example fits prediction = coefficient * feature + bias to one sample. Every variable used by the loop is declared here.
Register operator types once, create all persistent views, construct the optimizer, and add each trainable parameter view. RMSProp<2, 2> reserves state for two scalar values in at most two separately added views: the one-element coefficient view and the one-element bias view.
begin_graph() records the arena position after the persistent gradient buffers. It also starts a fresh record chain. Call it only after creating the views that must survive between iterations.
rewind_graph() discards the previous iteration's operation records by moving the arena position back to the mark. Parameter/output views and their gradient arrays remain valid. It resets tape status, enables recording, and clears the record-chain tail.
Each assignment computes caller-owned output values immediately and appends a small record containing the pointers needed by its derivative rule. The graph must be reevaluated every iteration because parameter values change after step().
Here the forward values are:
optimizer.zero_grad() clears every parameter gradient registered with the optimizer. Tape::backward() also resets gradients referenced by the recorded graph before installing its seed, so for a fixed, successful graph this call is usually redundant. It is nevertheless useful explicit training-loop hygiene: it also clears registered parameters omitted by a conditional graph, and it prevents stale gradients from reaching step() if graph structure is changed. Call it before backward(), not after, because after backward those buffers contain the gradients that step() must consume.
backward(loss) uses the default scalar seed d(loss)/d(loss) = 1. It first resets graph gradient buffers, seeds the loss gradient, and visits operation records in reverse creation order. Contributions use +=, so a parameter used by several nodes receives their sum.
For this graph:
Inputs have no gradient buffers, so derivatives for feature and target are not retained.
optimizer.step() reads the gradients, updates its fixed-size state, and then changes coefficient_value and bias_value in place. The next iteration's forward pass therefore uses the new parameter values.
To update once per batch, record every sample's prediction before constructing one loss over the complete prediction and target buffers. Scalar per-sample output views can share slices of persistent vector gradient buffers. The regression example in dsppp/Examples/autodiff_regression.cpp demonstrates this pattern with named coefficients, bias, polynomial, prediction, target, and loss views.
Inference still runs the operator forward kernels but does not need records:
The previous recording state is restored at the closing brace. See Concepts and memory model for the name and lifetime behavior.