Compiling with jit

JAX · Essentials · 12 min

jit is where JAX's speed comes from; uncompiled JAX code dispatches one operation at a time.

jax.jit(f) (or the @jax.jit decorator) compiles f with the XLA compiler. The first call traces the function: JAX runs your Python code with abstract placeholder values that carry only shape and dtype, records the array operations into an intermediate program, and compiles it for the device. Later calls with the same shapes and dtypes skip Python entirely and run the cached executable.

What you gain: operations get fused (no intermediate arrays written to memory), Python overhead disappears, and on GPU/TPU the whole function becomes a few kernels.

What you must accept:

  • The function should be pure. Side effects like print or appending to a list run only during tracing, not on every call.
  • Values are abstract during tracing, so Python if x > 0: on an array argument fails with a concretization error. Use jnp.where or jax.lax.cond (covered later).
  • A call with a new shape or dtype triggers a fresh trace and compile.

Because dispatch is asynchronous, time jitted code with .block_until_ready(), otherwise you measure only the launch. Always exclude the first (compiling) call from benchmarks.

A good rule: jit the largest pure function you can, typically a whole training or evaluation step, rather than tiny helpers.

Example

import time
import jax
import jax.numpy as jnp

def selu(x, alpha=1.67, lam=1.05):
    return lam * jnp.where(x > 0, x, alpha * jnp.exp(x) - alpha)

selu_jit = jax.jit(selu)
x = jnp.arange(1_000_000, dtype=jnp.float32) / 1e6

selu_jit(x).block_until_ready()          # first call: trace + compile
t = time.perf_counter()
selu_jit(x).block_until_ready()
print(f"jit:   {time.perf_counter() - t:.5f}s")

t = time.perf_counter()
selu(x).block_until_ready()
print(f"eager: {time.perf_counter() - t:.5f}s")  # typically several times slower

@jax.jit
def noisy(x):
    print("tracing!")        # runs only while tracing
    return x * 2
noisy(1.0); noisy(2.0)       # prints "tracing!" once

Remember

  • jax.jit traces once per input shape/dtype and caches compiled XLA code
  • Jitted functions must be pure; Python side effects run only at trace time
  • Benchmark with .block_until_ready() and skip the compile call

Watch out for

  • Jitting tiny functions called from a Python loop, paying dispatch overhead instead of jitting the loop body as a whole.

Check yourself

  1. A jitted function contains print(x). You call it 3 times with float32 arrays of the same shape. How often does it print?

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

Official docs: docs.jax.dev/en/latest/201/jit.html