How Autodiff Works
LeanPass uses reverse-mode automatic differentiation (backpropagation) to compute gradients. Here's what that actually means.
The Computation Graph
Every operation you perform on a Tensor records itself in a directed acyclic graph (DAG). The tensors are nodes, and the operations are edges.
a = Tensor([2.0], requires_grad=True)
b = Tensor([3.0], requires_grad=True)
c = a * b
d = c + 1.0
This builds a graph like:
a ──┐
├── [*] ── c ──┐
b ──┘ ├── [+] ── d ── 1.0
Each Tensor stores:
data— the computed valuegrad— the gradient accumulated during backward_prev— the parent tensors_op— the operation that produced this tensor_backward— a function that computes the local gradient
Forward Pass
During the forward pass, each operation computes its output value and stores a _backward function that knows how to propagate gradients through that specific operation.
# Internally, multiplication looks like:
def __mul__(self, other):
out = Tensor(self.data * other.data, ...)
def _backward():
if self.requires_grad:
self.grad += other.data * out.grad
if other.requires_grad:
other.grad += self.data * out.grad
out._backward = _backward
return out
Backward Pass
When you call d.backward(), LeanPass:
- Topologically sorts the graph — ordering nodes so parents come before children
- Seeds the gradient of the output tensor to
1.0 - Walks in reverse — calling each node's
_backwardfunction to propagate gradients to its parents
# Simplified backward algorithm
def backward(self):
topo = topological_sort(self)
self.grad = np.ones_like(self.data)
for node in reversed(topo):
node._backward()
The Chain Rule
At each node, the local gradient is computed using the chain rule. For a composition:
z = f(g(x))
dz/dx = dz/dg * dg/dx
Or for a multivariable function:
z = x * y
dz/dx = y (holding y constant)
dz/dy = x (holding x constant)
Example Trace
x = Tensor([2.0], requires_grad=True)
y = x ** 2
z = y + 3.0
z.backward()
Forward:
x = 2.0y = 4.0z = 7.0
Backward:
z.grad = 1.0(seed)y.grad += 1.0 * 1.0 = 1.0(d_z/d_y = 1.0 from+)x.grad += 1.0 * (2 * 2.0) = 4.0(d_y/d_x = 2*x from** 2)
Result: dz/dx = 4.0 — which matches the analytical derivative 2 * 2.0 = 4.0.