Debugging jitted code

JAX · Advanced · 12 min

print and pdb do not see runtime values under jit, so you need JAX's own debugging tools.

Inside jit, a normal print(x) shows a tracer, not values. JAX offers runtime-aware tools:

  • jax.debug.print("x = {x}", x=x) prints actual values each time the compiled function runs. It works under jit, vmap, grad and scan. Note the format string uses named or positional braces, not an f-string.
  • jax.debug.breakpoint() drops into a debugger with live values.
  • jax.debug.callback(fn, *args) calls arbitrary Python with concrete arrays, e.g. to log to a file.

For numeric problems:

  • jax.config.update("jax_debug_nans", True) makes any operation producing a NaN raise immediately; JAX then re-runs the function un-jitted to point at the offending line. It slows things down, so enable it only while hunting.
  • jax.config.update("jax_disable_jit", True) (or the jax.disable_jit() context manager) runs everything eagerly so ordinary print and pdb work.
  • jax.experimental.checkify adds functional error checks (checkify.check(x > 0, "msg")) that survive jit.

Two classic NaN sources: jnp.where with an unsafe branch (the gradient of the unused branch is still computed, so jnp.where(x > 0, jnp.log(x), 0.) gives NaN gradients at x <= 0; apply the where to the input first), and jnp.sqrt at exactly zero in a norm.

Ordering of jax.debug.print output is not guaranteed across independent operations; pass ordered=True if order matters.

Example

import jax
import jax.numpy as jnp

@jax.jit
def f(x):
    y = jnp.sin(x) * 2
    jax.debug.print("y = {y}", y=y)     # prints real values at run time
    return y.sum()

f(jnp.array([0.0, jnp.pi / 2]))         # y = [0. 2.]

def bad_log(x):
    return jnp.where(x > 0, jnp.log(x), 0.0)
print(jax.grad(bad_log)(0.0))           # nan: log's gradient is still evaluated

def safe_log(x):
    safe_x = jnp.where(x > 0, x, 1.0)   # sanitise the input first
    return jnp.where(x > 0, jnp.log(safe_x), 0.0)
print(jax.grad(safe_log)(0.0))          # 0.0

with jax.disable_jit():
    print(jax.jit(lambda x: x + 1)(1.0))  # runs eagerly: 2.0

Remember

  • jax.debug.print and jax.debug.breakpoint see runtime values under jit
  • jax_debug_nans pinpoints the first NaN; disable_jit makes ordinary tools work
  • Guard the inputs of jnp.where branches to avoid NaN gradients

Watch out for

  • Using an f-string with jax.debug.print, which formats the tracer at trace time instead of the runtime value.

Check yourself

  1. Why can jnp.where(x > 0, jnp.log(x), 0.0) produce NaN gradients at x = 0?

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/debugging.html