Automatic batching with vmap

JAX · Essentials · 10 min

vmap removes error-prone batch-dimension code and gives per-example gradients almost for free.

jax.vmap(f) vectorizes f over a leading batch axis. You write f for a single example; vmap produces a function that accepts a stack of examples and computes all of them at once, as efficiently as hand-written batched code, without a Python loop.

in_axes says which axis of each argument to map over; None means "broadcast this argument unchanged":

batched = jax.vmap(predict, in_axes=(None, 0))  # params shared, x batched

out_axes controls where the batch axis appears in the output (default 0). Arguments can be pytrees, with in_axes given per subtree.

Because transformations compose, vmap combines with the others:

  • jax.jit(jax.vmap(f)) compiles the batched function.
  • jax.vmap(jax.grad(loss), in_axes=(None, 0, 0)) gives per-example gradients, which is awkward in most frameworks.
  • Nested vmap maps over several axes, e.g. pairwise distances between two sets of points.

vmap traces the function like jit does, so the body should not branch in Python on array values. All mapped arguments must have the same size along their mapped axis, otherwise you get a clear ValueError.

Think of vmap as "write it for one, run it for many". It is often simpler and faster than reshaping arrays to fit a batched formula by hand.

Example

import jax
import jax.numpy as jnp

def predict(w, x):             # one example: x has shape (3,)
    return jnp.tanh(jnp.dot(w, x))

w = jnp.array([0.5, -1.0, 2.0])
xs = jnp.ones((4, 3))          # a batch of 4 examples

batched = jax.vmap(predict, in_axes=(None, 0))
print(batched(w, xs).shape)    # (4,)

def loss(w, x, y):
    return (predict(w, x) - y) ** 2

ys = jnp.zeros(4)
per_example = jax.vmap(jax.grad(loss), in_axes=(None, 0, 0))(w, xs, ys)
print(per_example.shape)       # (4, 3): one gradient per example

# pairwise distances with nested vmap
a, b = jnp.ones((2, 3)), jnp.zeros((5, 3))
dist = jax.vmap(jax.vmap(lambda p, q: jnp.linalg.norm(p - q), (None, 0)), (0, None))
print(dist(a, b).shape)        # (2, 5)

Remember

  • Write the function for one example; vmap adds the batch axis
  • in_axes=None broadcasts an argument such as shared parameters
  • vmap(grad(...)) yields per-example gradients

Watch out for

  • Mapping arguments with different sizes along the mapped axis, which raises a ValueError.

Check yourself

  1. f(w, x) handles a single x of shape (d,). Which call computes it for X of shape (n, d) with shared w?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.jax.dev/en/latest/_autosummary/jax.vmap.html