the path · 0/15
start the path

the path · chapter 01 of 15 · part i, the descent

JAX / PyTorch

Where you write math on whole arrays, and what tracing takes away from you.

mastery work · this chapter0/4
  1. go →auto
  2. go →auto
manual items are your word; auto items complete from your streaks, labs, and can-you ticks · stored in your browser only
the specimen · at this floor L0 · the program you wrote

The stack starts here, with one attention block on sixteen rows and no batch axis.

ROWS = 16
CHANNELS = 32
SEED = 0


def block(x, wq, wk, wv):
    q, k, v = x @ wq, x @ wk, x @ wv
    s = (q @ k.T) / jnp.sqrt(jnp.float32(q.shape[-1]))
    return jax.nn.softmax(s, axis=-1) @ v
read the whole artifact, and the levels above and below it →
bench/specimen/artifacts/source.py · lines 17 to 25 of 40 · python 3.12.0, jax 0.4.38, jaxlib 0.4.38
the layer

What this layer is

Everything starts as framework code: jnp operations on whole arrays, module trees, Python control flow. The essential fact about this layer is that it is not what runs. When you call jax.jit, JAX executes your Python once with tracer objects instead of numbers, records every primitive operation the tracers pass through, and keeps the recording. Your function becomes data.

Tracing explains most early JAX surprises. Python control flow that branches on values disappears into whichever branch the trace took. Shapes are fixed at trace time, which is why a new shape triggers a recompile. Print statements run once, at trace time, not per step. None of these are quirks; they are what "your function becomes data" means in practice.

PyTorch reaches this same stack through a different door: TorchTPU and torchax map ATen operations into StableHLOThe portable, versioned tensor IR that JAX and PyTorch both lower into; chapter 03 reads it line by line.taught in /l/stablehlo →, two layers below. By the time either framework reaches the compiler, the framework identity is gone. That shared convergence point is why everything you learn descending this stack applies to both.

the running example, used at every layer of this descent: naive attention
def attention(q, k, v):
    s = q @ k.T
    m = jnp.max(s, axis=-1, keepdims=True)
    p = jnp.exp(s - m)
    l = jnp.sum(p, axis=-1, keepdims=True)
    w = p / l
    return w @ v
the layer

What to take down the stack

Carry one question downward: which of these Python lines will still be visible three layers below, and in what form? The matmuls survive. The softmax dissolves into five primitives. The broadcasting that Python hid becomes explicit. The next layer, the jaxprThe traced program: one equation per primitive in single-assignment form, every shape and dtype stated.taught in /l/jaxpr →, is where you first see all of that written down.

go deeper, in order

Lessons

  1. 01What tracing takes, exactlyFour things leave your Python the moment a trace runs: closed-over arrays, side effects, shapes, and one side of every branch. ·
  2. 02Inside the tracerA description of a tracer is not the same thing as watching one run. This lesson is the watching: autodidax, the grammar, and seventeen real programs. ·
later on the path Stage 2 traces real programs through this layer (chapter 12). Keep descending; the path arrives there in order.