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

Debugging Impure Code: Error Messages Decoded

~10 min · purity, jax, tutorial

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

When you violate purity inside a JIT-compiled (or grad/vmap) function, JAX throws specific errors. Learning to read these error messages will dramatically speed up your debugging.

ConcretizationTypeError

This is the most common JAX error. It means you're trying to use a traced value (an abstract placeholder) in a context that requires a concrete Python value.

import jax
import jax.numpy as jnp

@jax.jit
def bad_fn(x):
    if x > 0:  # Needs concrete bool, but x is a tracer
        return x
    else:
        return -x

# ConcretizationTypeError: Abstract tracer value encountered
# where a concrete value is expected: Traced<ShapedArray(float32[])>
# This often arises when using Python control flow with traced values.

# Fix 1: Use jnp.where (no branching)
@jax.jit
def fix1(x):
    return jnp.where(x > 0, x, -x)

# Fix 2: Use jax.lax.cond (traced branching)
@jax.jit
def fix2(x):
    return jax.lax.cond(x > 0, lambda x: x, lambda x: -x, x)

TracerArrayConversionError

This error occurs when you try to convert a JAX tracer to a NumPy array or Python scalar during tracing.

import jax
import jax.numpy as jnp
import numpy as np

@jax.jit
def bad_conversion(x):
    # Trying to use a traced value as a Python int
    n = int(x.shape[0])  # This works (shape is static)
    val = int(x[0])       # ERROR: can't convert tracer to int
    return x[:val]

# Fix: keep everything in JAX-land
@jax.jit
def fixed(x):
    return jax.lax.dynamic_slice(x, (0,), (2,))

Leaked Tracer Error

This happens when a tracer escapes the scope of a transformation — usually by being stored in a global variable or external data structure.

import jax
import jax.numpy as jnp

stored_values = []

@jax.jit
def leaky_fn(x):
    y = x + 1
    stored_values.append(y)  # Tracer escapes into global list!
    return y

# This may raise a leaked tracer error or produce silent bugs
# Fix: don't store traced values outside the function

Debugging Strategy: Remove Transformations First

import jax
import jax.numpy as jnp

def my_function(x):
    """Some complex computation."""
    intermediate = jnp.sin(x) * 2
    # ... more code ...
    return jnp.sum(intermediate)

# Step 1: Test WITHOUT jit first
x = jnp.array([1.0, 2.0, 3.0])
result = my_function(x)  # If this works, the logic is correct
print(result)

# Step 2: Use print to inspect values
print(f"intermediate shape: {jnp.sin(x).shape}")

# Step 3: Add jit and check for errors
jitted = jax.jit(my_function)
result = jitted(x)

# Step 4: If jit fails, use jax.make_jaxpr to see the traced computation
print(jax.make_jaxpr(my_function)(x))

💡 Why This Matters

The best debugging strategy in JAX is progressive: first make your function work without any transformations, then add jit, then grad, then vmap. Each transformation adds constraints. If something breaks at the jit step, you know the issue is a purity violation. If it breaks at the grad step, the issue is likely non-differentiable operations.

Code

import jax
import jax.numpy as jnp

@jax.jit
def bad_fn(x):
    if x > 0:  # Needs concrete bool, but x is a tracer
        return x
    else:
        return -x

# ConcretizationTypeError: Abstract tracer value encountered
# where a concrete value is expected: Traced<ShapedArray(float32[])>
# This often arises when using Python control flow with traced values.

# Fix 1: Use jnp.where (no branching)
@jax.jit
def fix1(x):
    return jnp.where(x > 0, x, -x)

# Fix 2: Use jax.lax.cond (traced branching)
@jax.jit
def fix2(x):
    return jax.lax.cond(x > 0, lambda x: x, lambda x: -x, x)
import jax
import jax.numpy as jnp
import numpy as np

@jax.jit
def bad_conversion(x):
    # Trying to use a traced value as a Python int
    n = int(x.shape[0])  # This works (shape is static)
    val = int(x[0])       # ERROR: can't convert tracer to int
    return x[:val]

# Fix: keep everything in JAX-land
@jax.jit
def fixed(x):
    return jax.lax.dynamic_slice(x, (0,), (2,))
import jax
import jax.numpy as jnp

stored_values = []

@jax.jit
def leaky_fn(x):
    y = x + 1
    stored_values.append(y)  # Tracer escapes into global list!
    return y

# This may raise a leaked tracer error or produce silent bugs
# Fix: don't store traced values outside the function
import jax
import jax.numpy as jnp

def my_function(x):
    """Some complex computation."""
    intermediate = jnp.sin(x) * 2
    # ... more code ...
    return jnp.sum(intermediate)

# Step 1: Test WITHOUT jit first
x = jnp.array([1.0, 2.0, 3.0])
result = my_function(x)  # If this works, the logic is correct
print(result)

# Step 2: Use print to inspect values
print(f"intermediate shape: {jnp.sin(x).shape}")

# Step 3: Add jit and check for errors
jitted = jax.jit(my_function)
result = jitted(x)

# Step 4: If jit fails, use jax.make_jaxpr to see the traced computation
print(jax.make_jaxpr(my_function)(x))

External links

Exercise

Reproduce three common impurity errors on purpose: (1) ConcretizationTypeError from a global, (2) TracerArrayConversionError from list-of-tracers, (3) silent stale cache from print as a 'side channel'. For each, write the fix and a one-line moral.

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.