C.W.K.
Stream
Lesson 01 of 05 · published

What grad Does: Functions In, Gradient Functions Out

~8 min · grad, jax, tutorial

Level 0Curious
0 XP0/73 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete

In calculus, taking the derivative of a function gives you a new function. JAX's jax.grad does exactly this: it takes a Python function that returns a scalar, and returns a new Python function that computes the gradient.

import jax
import jax.numpy as jnp

# A scalar-valued function
def f(x):
    return x ** 2

# grad(f) returns a NEW function that computes df/dx
df = jax.grad(f)

print(f(3.0))    # 9.0
print(df(3.0))   # 6.0 (derivative of x^2 is 2x, evaluated at x=3)

# df is a regular Python function — you can call it, JIT it, etc.
print(df(5.0))   # 10.0

This is fundamentally different from PyTorch's approach. In PyTorch, you compute a loss, call loss.backward(), and then read param.grad. The gradient is attached to the tensor. In JAX, grad is a function transformation — it takes a function and returns a function. No state is mutated anywhere.

import jax
import jax.numpy as jnp

# Multi-variable function
def loss(params, x, y):
    w, b = params
    pred = jnp.dot(x, w) + b
    return jnp.mean((pred - y) ** 2)

# grad differentiates with respect to the FIRST argument by default
grad_fn = jax.grad(loss)

# Call it: returns gradient with same structure as params
w = jnp.array([1.0, 2.0])
b = jnp.array(0.0)
params = (w, b)
x = jnp.array([[1.0, 0.5], [0.3, 0.8]])
y = jnp.array([1.0, 0.5])

grads = grad_fn(params, x, y)
print(type(grads))       # tuple — same structure as params!
print(grads[0].shape)    # (2,) — gradient of w
print(grads[1].shape)    # () — gradient of b

💡 Why This Matters

The functional style of grad has a profound consequence: gradients naturally inherit the structure of the parameters. If your params are a nested tuple of arrays, the gradient is a nested tuple of arrays with matching shapes. This works with any pytree structure — dicts, named tuples, custom classes — without any special registration. It's why JAX doesn't need a Parameter class like PyTorch.

Code

import jax
import jax.numpy as jnp

# A scalar-valued function
def f(x):
    return x ** 2

# grad(f) returns a NEW function that computes df/dx
df = jax.grad(f)

print(f(3.0))    # 9.0
print(df(3.0))   # 6.0 (derivative of x^2 is 2x, evaluated at x=3)

# df is a regular Python function — you can call it, JIT it, etc.
print(df(5.0))   # 10.0
import jax
import jax.numpy as jnp

# Multi-variable function
def loss(params, x, y):
    w, b = params
    pred = jnp.dot(x, w) + b
    return jnp.mean((pred - y) ** 2)

# grad differentiates with respect to the FIRST argument by default
grad_fn = jax.grad(loss)

# Call it: returns gradient with same structure as params
w = jnp.array([1.0, 2.0])
b = jnp.array(0.0)
params = (w, b)
x = jnp.array([[1.0, 0.5], [0.3, 0.8]])
y = jnp.array([1.0, 0.5])

grads = grad_fn(params, x, y)
print(type(grads))       # tuple — same structure as params!
print(grads[0].shape)    # (2,) — gradient of w
print(grads[1].shape)    # () — gradient of b

External links

Exercise

Hand-derive the gradient of f(x, y) = sin(x) * cos(y). Then call jax.grad(f, argnums=(0, 1))(x, y) and compare numerically. The agreement is your sanity check that you understand argnums. Then break it on purpose: pass a non-scalar output and watch the error.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.