Cheat sheets

JAX against PyTorch

Eager tape autograd against traced, compiled pure functions. The same MNIST MLP in both, with APIs and gotchas.

  • Tasks and commands
  • Annotated code
  • Diagrams
  • Comparisons
  • Troubleshooting
  • Questions and tips

Source

Side by side#

FactorPyTorchJAX
AutodiffTape-based autogradFunctional transforms
ExecutionEager (C++ kernels)Traced + JIT compiled
Modelnn.ModulePure function: f(params, x) -> y
TPU supportCommunity-maintained (torch_xla)First-class
GPU supportBest-in-class (native CUDA)Good (CUDA via XLA)
DebuggingEasy (eager, line-by-line)Hard (tracing + compilation)
Speed (MNIST MLP)~0.5s/epoch~0.3s/epoch
Multi-deviceFSDPjax.pmap(f)

From Lesson 03.11, Lesson 03.12

Same task, two APIs#

TaskPyTorchJAX
Get gradientsloss.backward()jax.grad(loss_fn)(params, x, y)
Clear gradientsoptimizer.zero_grad()Not needed. Grads are return values
Update paramsoptimizer.step()optax.apply_updates(params, updates)
Adamtorch.optim.Adam(model.parameters(), lr=1e-3)optax.adam(learning_rate=1e-3)
Batch over examplesfor x in batch:jax.vmap(f)
Hold paramsmodel.parameters()Immutable pytree of arrays
Seed randomnesstorch.manual_seedjax.random.PRNGKey(42)
Save weightstorch.save(model.state_dict(), "model.pt")checkpointer.save('/tmp/model', params)

From Lesson 03.11, Lesson 03.12

Two execution models#

ops.gradtraceprocessmodel(x)runs now, eagerstoreTaperecords each oploss.backward()replays tape inreverseoptimizer.step()mutates paramsPure functionf(params, x) -> yjax.gradreturns a gradfunctionjax.jittraces on first callgpuXLAfused machine codeops.gradtraceprocessmodel(x)runs now, eagerstoreTaperecords each oploss.backward()replays tape in reverseoptimizer.step()mutates paramsPure functionf(params, x) -> yjax.gradreturns a grad functionjax.jittraces on first callgpuXLAfused machine code

From Lesson 03.11, Lesson 03.12

PyTorch: MNIST MLP loop#

model = MNISTModel().to(device)784, 256, 128, 10
criterion = nn.CrossEntropyLoss()takes raw logits
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(10):
model.train()dropout on
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()grads accumulate
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()replays the tape
optimizer.step()
torch.save(model.state_dict(), "mnist_mlp.pt")~97.8% test accuracy

From Lesson 03.11, 03.11 code/pytorch_intro.py

JAX transforms#

TaskCode
Differentiate a functionjax.grad(f)
Second derivativejax.grad(jax.grad(f))
Batch a one-example functionjax.vmap(predict, in_axes=(None, 0))
Per-example gradientsjax.vmap(jax.grad(loss_fn), in_axes=(None, 0, 0))
Split the batch over devicesjax.pmap(train_step, axis_name='devices')
Update every leafjax.tree.map(lambda p, g: p - lr * g, params, grads)
Set one elementa = a.at[0].set(5)
Clip, then Adamoptax.chain(optax.clip_by_global_norm(1.0), optax.adam(1e-3))

From Lesson 03.12

One JAX training step#

1init_paramsrandom.split(key, 3) gives three keys. He scaleis jnp.sqrt(2.0 / 784).2optimizer.initopt_state = optimizer.init(params) holds theAdam state.3jax.value_and_gradreturns the loss and the grads in one pass.4optimizer.updateturns the grads into updates and a newopt_state.5optax.apply_updatesreturns new params. Nothing is mutated.6First callJAX traces train_step and compiles it to XLA.The first epoch is slow.7Later callsskip Python. Epochs 2-10 are fast. 10 epochsgive ~97% test accuracy.

From Lesson 03.12, 03.12 code/jax_intro.py

JAX: MNIST MLP loop#

optimizer = optax.adam(learning_rate=1e-3)
@jax.jitcompile to XLA
def train_step(params, opt_state, x, y):
loss, grads = jax.value_and_grad(loss_fn)(params, x, y)
updates, opt_state = optimizer.update(grads, opt_state, params)
params = optax.apply_updates(params, updates)
return params, opt_state, lossnew state out
key = random.PRNGKey(0)
params = init_params(key)nested dict pytree
opt_state = optimizer.init(params)
for epoch in range(n_epochs):
key, subkey = random.split(key)new key each epoch
perm = random.permutation(subkey, len(X_train))
params, opt_state, loss = train_step(params, opt_state, xb, yb)batch_size 128

From 03.12 code/jax_intro.py, Lesson 03.12

Install and run#

CommandWhat it does
pip install jax jaxlib optax flaxInstalls JAX, Optax and Flax
pip install jax[cuda12]Installs JAX with GPU support
pip install jax[tpu] -f <url>Installs JAX for TPU on Google Cloud
XLA_PYTHON_CLIENT_PREALLOCATE=falseStops the 75% GPU memory preallocation
jax.debug.print()Prints inside a jitted function
fast_fn(x).block_until_ready()Waits for the result before you stop a timer
torch.load("model.pt", weights_only=True)Loads a saved state dict

From Lesson 03.12, 03.12 code/jax_intro.py, Lesson 03.11

Gotchas#

SymptomCauseFix
Expected all tensors to be on the same deviceModel and batch on different devicesMove both with .to(device)
Wrong gradients and no errorSoftmax output into CrossEntropyLossPass raw logits
First step is slowJIT compiles on the first callWarm up before you benchmark
if x > 0 fails inside JITControl flow depends on a traced valueUse jax.lax.cond
Python loop inside JIT is slowLoop over JAX arraysUse jax.lax.scan or jax.lax.fori_loop
a[0] = 5 failsJAX arrays are immutableWrite a = a.at[0].set(5)

From Lesson 03.11, Lesson 03.12

Use X when#

  • Use PyTorch unless you have a specific reason to use JAX.
  • Use PyTorch to debug line by line. print(), pdb and breakpoint() work.
  • Use PyTorch to prototype fast and for the HuggingFace ecosystem.
  • Use JAX when you have TPU access.
  • Use JAX for per-example gradients.
  • Use JAX for multi-device training at massive scale.

From Lesson 03.12, Lesson 03.11

Questions#

Why does JAX need pure functions?
JIT compilation requires them. Same inputs give the same outputs.
Why is there no zero_grad in JAX?
jax.grad returns grads as values. Tensors store no graph.
Why save state_dict(), not the model?
The model object uses pickle. Pickle breaks when you refactor.
Why torch.no_grad() in eval?
It disables autograd. Memory use goes down.
What maps from the mini framework?
Sequential, Adam, zero_grad, backward, step, train, eval.

From Lesson 03.10, Lesson 03.11, Lesson 03.12