Skip to content

← JAX Autodiff step 6 of 25

Medium Primitives

grad vs vjp

Jump to the task

Recover a gradient from a pullback

jax.grad gives the gradient of a scalar loss. jax.vjp gives you the loss value and a function called a pullback. For a real scalar loss, calling that pullback with a weight of one gives the same mathematical gradient. A weight of three gives three times the gradient.

import jax
import jax.numpy as jnp

x = jnp.array([1.0, 2.0])

def loss(w):
    return jnp.sum((w * x) ** 2)

value, pullback = jax.vjp(loss, 1.0)
# value is 5.0
pullback(1.0)[0]       # 10.0
jax.grad(loss)(1.0)    # 10.0
pullback(3.0)[0]       # 30.0

Here loss(w) = 5*w**2, whose derivative at w=1 is 10. The weight passed to the pullback is called a cotangent. It scales that derivative; it does not change the w at which the derivative is evaluated.

Match the output, then unpack the inputs

The loss output has shape (), so the cotangent must also be scalar. A Python float works in this example. A zero-dimensional JAX array also works: jnp.ones_like(value) supplies one with the output's shape and dtype. A length-one array has shape (1,) and is not interchangeable.

The pullback returns a tuple with one entry for each argument passed to jax.vjp. Our inner function accepts only w, so [0] extracts its scalar derivative. Capturing b and x from the surrounding function keeps them fixed for this derivative.

Problem

Implement grad_via_vjp(w, b, x) using jax.vjp. The real floating-point scalars w and b and one-dimensional floating-point array x define loss(w) = sum((w*x + b)**2).

Return the scalar derivative with respect to w only, holding b and x fixed. Apply a unit cotangent to the pullback. Use a sum, not a mean.

For w=1.0, b=2.0, and x=[3.0], the result is 2 * (1*3 + 2) * 3 = 30.0.

The result should agree with jax.grad(loss)(w) within floating-point tolerance. Practice using the VJP even though numerical output checks cannot distinguish it from an equivalent formula.