[jv] Convert to JAX and content checks#618
Conversation
|
📖 Netlify Preview Ready! Preview URL: https://pr-618--sunny-cactus-210e3e.netlify.app (fc998f6) 📚 Changed Lecture Pages: jv |
|
📖 Netlify Preview Ready! Preview URL: https://pr-618--sunny-cactus-210e3e.netlify.app (731446f) 📚 Changed Lecture Pages: jv |
|
📖 Netlify Preview Ready! Preview URL: https://pr-618--sunny-cactus-210e3e.netlify.app (d24a951) 📚 Changed Lecture Pages: jv |
|
📖 Netlify Preview Ready! Preview URL: https://pr-618--sunny-cactus-210e3e.netlify.app (9ffac19) 📚 Changed Lecture Pages: jv |
|
🤖 Status note for a future session — from a maintainer investigation on 2026-07-08 into why open-PR previews 404. Context only, not instructions. Netlify preview: https://pr-618--sunny-cactus-210e3e.netlify.app/ currently returns 404. Why previews are down (repo-wide findings)1. This branch is stale — 178 commits behind 2. The arviz failure was a red herring — do NOT pin arviz or rewrite plotting. A 2026-07-07 rebuild also failed in Recommended first step for this PRUpdate this branch to This PR touches: |
|
Thanks @HumphreyYang — another solid conversion, and the core of it is genuinely better than what it replaces. Apologies for the delay on this one too. I checked the numerics against the original Numba implementation (holding
A few things I'd like to resolve before merge, then some optional points. Requests1. The 45-degree diagram loop has become dramatically slower. for x in plot_grid:
for i in range(jv.mc_size):
key, subkey = jr.split(key)
b = 1 if jr.uniform(subkey) < π(s(x)) else 0
u = f_rvs[i]
y = h(x, b, u)
ax.plot(x, y, 'go', alpha=0.25)Every one of the 5,000 iterations now does a
So it's ~128× slower than the code it replaces. This is the "JAX inside a Python loop" pattern — outside Since key = jr.key(0)
b = jr.uniform(key, (plot_grid_size, jv.mc_size)) < π(s(plot_grid))[:, None]
gx = g(jv, plot_grid, ϕ(plot_grid))[:, None]
y = jnp.where(b, jnp.maximum(gx, f_rvs[None, :]), gx)
ax.plot(jnp.repeat(plot_grid, jv.mc_size), y.ravel(), 'go', alpha=0.25)That also replaces 5,000 individual 2. The exercise statement still refers to the removed constructor. The non-executed jv = JVWorker(grid_size=25, mc_size=50)
3. The two functions share about twenty lines verbatim — the @jax.jit
def _search(jv, x, v):
"""Grid search over feasible (s, ϕ); returns best value and best pair."""
search_grid = jnp.linspace(jv.ɛ, 1, 15)
s_vals, ϕ_vals = jnp.meshgrid(search_grid, search_grid)
pairs = jnp.stack([s_vals.ravel(), ϕ_vals.ravel()], axis=1)
def objective(s_ϕ):
s, ϕ = s_ϕ
value = state_action_values(jv, s_ϕ, x, v)
return jnp.where(s + ϕ <= 1.0, value, -jnp.inf)
values = jax.vmap(objective)(pairs)
i = jnp.argmax(values)
return values[i], pairs[i]
@jax.jit
def T(jv, v):
return jax.vmap(lambda x: _search(jv, x, v)[0])(jv.x_grid)
@jax.jit
def get_greedy(jv, v):
policies = jax.vmap(lambda x: _search(jv, x, v)[1])(jv.x_grid)
return policies[:, 0], policies[:, 1]Worth doing in a lecture people read closely. For consideration only
ContextWe've consolidated our JAX guidance into a single page — QuantEcon/QuantEcon.manual#113. The Python-loop point in request 1 is written up there explicitly, along with device placement, Thanks again — the |
|
Thanks again @HumphreyYang — closing this in favour of #984, but I want to be clear that this PR did most of the useful work. Reviewing it opened up a broader question about when a lecture should use JAX at all, and that discussion ended up reshaping our guidance rather than just this one file. By the time we'd worked through it there were enough changes to make a fresh branch off Your work is the starting point for #984 and a lot of it survives intact:
You're credited as co-author on the commit. The main structural change is writing the Bellman right-hand side as a scalar function of one state and one action pair, then applying Two corrections to things I said above, since they shouldn't stand uncorrected: The CPU pin. I asked you to justify My "~128× slower" figure for the 45 degree diagram. That measurement excluded the 5,000 individual Please do review #984 — and push back if you think the nested- |
|
Many thanks for kicking this off @HumphreyYang , much appreciated. I went for a nested vmap, admittedly a taste consideration. In the final version I'm going to emphasize that jax doesn't actually buy us speed gains for such small grids, but it scales much better than numpy. |
Rewrites the on-the-job search lecture to use JAX on the GPU, replacing the JVWorker class, operator_factory, and the nested prange/for loops. The Bellman right-hand side is written as a scalar function _B of one state x and one action pair (s, ϕ), so it reads like the equation it implements. Three applications of jax.vmap then vectorize it over ϕ, s and x in turn, playing the role of a triple loop. T and get_greedy both reduce over the result, which removes the duplicated grid-search code the two functions previously carried. Other changes: - Model primitives move to a NamedTuple with a factory function, and the two action grids are stored alongside the state grid. - solve_model uses a bounded jax.lax.while_loop and returns the iteration count and final error so callers can check convergence. - The 45 degree diagram in Exercise 1 draws all realizations in one vectorized call rather than looping over states and draws, and plots them with a single ax.plot rather than 5,000. - Exercise 2 evaluates w*(ϕ) on the grid rather than via a list comprehension. - Adds the shared GPU admonition. Validated against the Numba implementation on main, using identical draws from f: both converge in 205 iterations, the value functions agree to 7.5e-06 (relative 6.2e-07), and the s and ϕ policies select identical grid points at every state. Checked float32 against float64 -- the policies are unchanged and Monte Carlo error at mc_size=100 dominates, so the default precision is kept. All twelve code cells execute on an RTX 4080, and the three figures reproduce the behaviour the surrounding prose describes. Supersedes #618. Co-authored-by: Humphrey Yang <u6474961@anu.edu.au> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This PR updates
jvby converting the code to JAX.Standard convertion similar to preceding lectures.