This article assumes what it means for a function to be represented as a directed acyclic graph (DAG).
E.g. the function can be represented as a DAG with the nodes defined like so:
Now let's say the goal is to compute the derivative of the output with respect to each input : . It has a nice visual interpretation in terms of the DAG:
If each edge carries the local derivative , then is the sum over paths from to of the product of edge derivatives along each path.
It also leads to the following insights:
First, let us assign to the partial derivative .
Remember that is the sum over all paths products. To calculate it, we can follow these steps:
Note that these sums can be computed in primarily two ways:
Let's count the number of multiplications and additions needed for each approach.
Forward mode (JVP) needs one sweep per input (seed ). Reverse mode (VJP) needs one sweep per output (seed ).
| JVP | VJP | |
|---|---|---|
| Sweep for | (3 muls, 0 adds) | — |
| Sweep for | (5 muls, 1 add) | — |
| Sweep for | (3 muls, 0 adds) | — |
| Backward sweep | — | (7 muls, 1 add) |
| Total | (11 muls, 1 add) | (7 muls, 1 add) |
Put the tally on the canvas:
VJP needs one sweep, not three, so it wins as inputs grow. In general, VJP is more efficient for a function with many inputs and few outputs.
If the edges of the DAG are reversed, one gets a function from to .
The edge derivatives are also switched for . What was JVP is now VJP and vice versa. In this case, JVP wins. In general, VJP is more efficient for a function with few inputs and many outputs. In practice this rarely happens, if at all.