Skip to content

← JAX Autodiff step 5 of 25

Medium Primitives

Build a Jacobian with VJPs

Jump to the task

Select one output at a time

A vector-Jacobian product combines the derivatives of several outputs using weights called cotangents. To get the derivatives of just one output, give that output weight 1 and all the others weight 0. These weights form a standard basis vector.

import jax
import jax.numpy as jnp

def f(x):
    return jnp.array([x[0] + x[1], x[0] * x[1]])

x = jnp.array([2.0, 3.0])
value, pullback = jax.vjp(f, x)
basis = jnp.eye(2, dtype=value.dtype)
row0 = pullback(basis[0])[0]  # [1.0, 1.0]
row1 = pullback(basis[1])[0]  # [3.0, 2.0]
J = jnp.stack([row0, row1])

basis[0] is [1, 0], so it selects the sum's derivatives. basis[1] is [0, 1], so it selects the product's derivatives. Stacking these results gives the Jacobian, with one row per output and one column per input.

The pullback returns a tuple with one entry per argument supplied to jax.vjp. Here there is only one input argument, so [0] extracts its array. Each cotangent must match the output's shape: length 2 in this example, and length 3 in the exercise below.

Reuse the pullback

Call jax.vjp once at the input where you want the Jacobian, then apply its pullback to each output basis vector. The three-row exercise can use a fixed Python loop. Under JIT, that fixed loop is traced and its operations enter the compiled computation.

You can also batch the basis vectors with jax.vmap(pullback)(basis)[0]. Here vmap batches output weights, not a batch of different input points. This is the row-building idea behind jax.jacrev. Batching can help performance, but it does not guarantee a speedup over a compiled loop or imply one separate device pass for every output.

Problem

Implement jacobian_via_vjp(x) for a one-dimensional real floating-point JAX array x with shape (d,). Define a function returning the JAX array [sum(x), sum(2*x), sum(x**2)].

Create one pullback with jax.vjp and apply it to the three standard basis vectors of the output space. Stack the resulting rows and return a JAX array with shape (3, d). A fixed loop or vmap can apply the pullback to those vectors.

The rows, in order, are ones, twos, and 2*x. For x=[-1.0, 3.0], the expected result is:

[[ 1.0, 1.0],
 [ 2.0, 2.0],
 [-2.0, 6.0]]

Do not use a single all-ones cotangent: it sums the rows into one vector instead of keeping the outputs separate. Practice the pullback construction even though numerical tests cannot enforce API choice.