This issue is the public record of why every jax install in this repo is pinned to 0.11.0, what the underlying jax/XLA regression actually is, and what has to be true before the pins can lift. It exists so the upstream report has a public reference for where the bug was found and how it was diagnosed.
The regression, in one paragraph
Since jax/jaxlib 0.11.1 (PyPI 2026-08-17), a dynamic-update-slice that writes a small slice (< 256 bytes) into a large array inside a loop body costs O(whole buffer) per iteration on the CPU backend instead of O(update). In the standard lax.fori_loop / lax.scan accumulation idiom the buffer length equals the trip count, so runtime becomes quadratic in the loop length — ~4x per doubling of n. This lecture's numpy_vs_numba_vs_jax.md runs exactly that idiom at n = 10,000,000: ~0.06 s under 0.11.0, extrapolated ~18 hours under 0.11.1, so every executing build dies on CellTimeoutError at the 600 s myst-nb limit.
How it surfaced and what landed
| date (UTC) |
event |
| 2026-08-17 20:29 |
jax/jaxlib 0.11.1 hit PyPI |
| 2026-08-18 |
fr/fa translation cache builds began timing out (their builds omit -W, so five timeout builds concluded green before the publish path failed); en's daily execution-linux.yml went red the same day |
| 2026-08-19 |
bisected to the jax version and pinned jax[cuda13]==0.11.0 in cache.yml / ci.yml / publish.yml (#617); translations pinned jax==0.11.0 in the same three workflows each (lecture-python-programming.fr#37, .fa#157, .zh-cn#95) |
| 2026-08-20 |
the three remaining unpinned workflows execution-{linux,osx,win}.yml pinned via #620, after which a dispatched execution run installed 0.11.0 and executed the lecture in 5.84 s; #621 opened to pin the lecture's own !pip install cell, which is what Colab readers execute (in CI that cell is a no-op because jax is pre-installed; in Colab it resolves 0.11.1) |
All six workflows in this repo and all nine translation workflows now pin 0.11.0. Pinning jax alone pins jaxlib too: jax==0.11.0 requires jaxlib<=0.11.0,>=0.11.0 on the base requirement and on every accelerator extra, and the pairing is enforced again at import in both directions.
Root cause, condensed
The full report with the reproduction script and measurement tables is being filed upstream at jax-ml/jax (link to follow in a comment). The short version, all measured:
- Bisected to a single nightly build:
0.11.1.dev20260725 good (n=200,000 fori 0.0014 s), 0.11.1.dev20260726 bad (13.24 s) — ~10,000x apart, monotonic on both sides, reproduced on linux/x86_64 and linux/aarch64.
- It is jaxlib, not jax: mixing nightly packages, runtime tracks the jaxlib version only, and the optimized HLO is byte-identical between fast and slow pairings. The boundary corresponds to the XLA roll
6b5d5254... -> 88e9a7db..., a window of exactly 10 commits, 4 tagged [XLA:CPU].
- The trigger is the DUS, not the loop:
fori_loop, scan and while_loop with scalar bodies are unaffected on 0.11.1; the identical scan with its stacked ys output discarded is unaffected; only bodies writing a small slice into a large buffer regress. Cost is O(buffer x trips) with a sharp cliff at exactly 256 bytes of update width (holds across float16/32/64 — a byte threshold, not an element count).
- No flag-level workaround exists:
--xla_cpu_use_fusion_emitters was deprecated inside the same XLA window, and the surviving related flags measurably change nothing. The only mitigations are pinning, discarding stacked outputs, or batching writes to >= 256 bytes per iteration.
Unpin condition
A jax release must ship whose jitted lax.fori_loop at n=400,000 on CPU completes in ~single-digit seconds rather than ~100 s (the two sides differ by ~65x, so the gate is unambiguous; a validated one-command container test exists). As of 2026-08-20 no such release exists — 0.11.1 is latest, and the nightly 0.11.2.dev20260819 still carries the regression at ~96% of 0.11.1's runtime, so a hypothetical 0.11.2 cut from current main would ship broken. When the upstream fix lands, unpin the six workflows here, the nine translation workflows, and the lecture's install cell together.
Diagnosed with assistance from Anthropic's Claude models (Opus 5 and Fable 5).
This issue is the public record of why every jax install in this repo is pinned to
0.11.0, what the underlying jax/XLA regression actually is, and what has to be true before the pins can lift. It exists so the upstream report has a public reference for where the bug was found and how it was diagnosed.The regression, in one paragraph
Since jax/jaxlib 0.11.1 (PyPI 2026-08-17), a
dynamic-update-slicethat writes a small slice (< 256 bytes) into a large array inside a loop body costs O(whole buffer) per iteration on the CPU backend instead of O(update). In the standardlax.fori_loop/lax.scanaccumulation idiom the buffer length equals the trip count, so runtime becomes quadratic in the loop length — ~4x per doubling of n. This lecture'snumpy_vs_numba_vs_jax.mdruns exactly that idiom at n = 10,000,000: ~0.06 s under 0.11.0, extrapolated ~18 hours under 0.11.1, so every executing build dies onCellTimeoutErrorat the 600 s myst-nb limit.How it surfaced and what landed
-W, so five timeout builds concluded green before the publish path failed); en's dailyexecution-linux.ymlwent red the same dayjax[cuda13]==0.11.0incache.yml/ci.yml/publish.yml(#617); translations pinnedjax==0.11.0in the same three workflows each (lecture-python-programming.fr#37, .fa#157, .zh-cn#95)execution-{linux,osx,win}.ymlpinned via #620, after which a dispatched execution run installed 0.11.0 and executed the lecture in 5.84 s; #621 opened to pin the lecture's own!pip installcell, which is what Colab readers execute (in CI that cell is a no-op because jax is pre-installed; in Colab it resolves 0.11.1)All six workflows in this repo and all nine translation workflows now pin
0.11.0. Pinning jax alone pins jaxlib too:jax==0.11.0requiresjaxlib<=0.11.0,>=0.11.0on the base requirement and on every accelerator extra, and the pairing is enforced again at import in both directions.Root cause, condensed
The full report with the reproduction script and measurement tables is being filed upstream at jax-ml/jax (link to follow in a comment). The short version, all measured:
0.11.1.dev20260725good (n=200,000 fori 0.0014 s),0.11.1.dev20260726bad (13.24 s) — ~10,000x apart, monotonic on both sides, reproduced on linux/x86_64 and linux/aarch64.6b5d5254...->88e9a7db..., a window of exactly 10 commits, 4 tagged[XLA:CPU].fori_loop,scanandwhile_loopwith scalar bodies are unaffected on 0.11.1; the identicalscanwith its stackedysoutput discarded is unaffected; only bodies writing a small slice into a large buffer regress. Cost is O(buffer x trips) with a sharp cliff at exactly 256 bytes of update width (holds across float16/32/64 — a byte threshold, not an element count).--xla_cpu_use_fusion_emitterswas deprecated inside the same XLA window, and the surviving related flags measurably change nothing. The only mitigations are pinning, discarding stacked outputs, or batching writes to >= 256 bytes per iteration.Unpin condition
A jax release must ship whose jitted
lax.fori_loopat n=400,000 on CPU completes in ~single-digit seconds rather than ~100 s (the two sides differ by ~65x, so the gate is unambiguous; a validated one-command container test exists). As of 2026-08-20 no such release exists — 0.11.1 is latest, and the nightly0.11.2.dev20260819still carries the regression at ~96% of 0.11.1's runtime, so a hypothetical 0.11.2 cut from current main would ship broken. When the upstream fix lands, unpin the six workflows here, the nine translation workflows, and the lecture's install cell together.Diagnosed with assistance from Anthropic's Claude models (Opus 5 and Fable 5).