Skip to content

PROJECT: Characterise JAX on cloud CI runners — GPU fit, concurrency, and build guidance #350

Description

@mmcky

Why now

Two independent JAX-on-cloud failures surfaced in the space of a week, in different repos, with different root causes, at different layers of the stack. Each was invisible until it burned hours of on-demand GPU runner time, and neither would have been predicted by "it works on a laptop" or "it works on Colab".

incident repo symptom root cause status
NUTS cell timeout QuantEcon/lecture-python.myst#1022 jax + numpyro cell >2400s on the T4, 157s on a laptop CPU; GPU ≥2.7x slower than the CPU in the same box unconfirmed — leading hypotheses are crippled FP64 on inference-class GPUs and kernel-launch overhead on 10x10 ops worked around by pinning the lecture to CPU
Concurrent kernel death QuantEcon/lecture-python-programming#363 jax_intro kernel aborts with no traceback on every cold-cache build confirmed by an instrumented run — three JAX kernels share one T4, the first preallocates JAX's default 75% (11.3GiB of 15.3GiB), the loser clamps to the ~3.4GiB remainder and aborts when a cell outgrows it fixed with XLA_PYTHON_CLIENT_PREALLOCATE=false, validation run in progress

The two incidents share a pattern worth naming: our lecture code is written against a mental model of "a GPU makes JAX fast", but the runners are single inference-class GPUs (T4 on g4dn.2xlarge) shared by concurrent notebook execution, running workloads that are frequently small-matrix, float64, and latency-bound rather than throughput-bound. We do not currently have a tested model of when that combination wins, loses, or crashes — we find out one incident at a time.

Proposed project

A focused investigation to characterise JAX on our cloud CI runners and turn the findings into standing guidance and CI configuration, rather than per-incident firefighting.

Questions to answer

  1. FP64 penalty. How large is the float64 penalty on the T4 for our actual workload shapes (Kalman filters, NUTS, small linear models)? T4 FP64 is nominally 1/32 of FP32 throughput — measure what that does to representative lecture cells, and whether jax_enable_x64 lectures should default to CPU.
  2. Small-op / launch overhead. At what array size does the T4 break even with the runner's own CPU for our common op mix? A lax.scan of 10x10 ops may never amortise launch latency — find the crossover so lecture authors have a rule of thumb rather than a guess.
  3. Concurrency policy. myst executes notebooks at parallelism = cpus-1, so multiple JAX kernels share one GPU. With preallocation off, what is the safe concurrency for JAX lectures, and should JAX-heavy lectures be serialised while pure-CPU lectures fan out?
  4. Sampler configuration. Does chain_method='vectorized' close the GPU gap for numpyro workloads, or is sequential + CPU the right default for small models?
  5. Standard CI environment. Which XLA/JAX env vars should be standard across all GPU lecture repos (XLA_PYTHON_CLIENT_PREALLOCATE, possibly XLA_PYTHON_CLIENT_MEM_FRACTION, allocator choice), and where do they belong so every repo inherits them rather than rediscovering them?
  6. Instance fit. Is g4dn.2xlarge the right instance at all? Candidates to price against: g5/g6 (better FP64 story is not automatic — check), plain CPU instances for FP64-heavy lectures, or a split fleet where only genuinely large-array lectures get a GPU.
  7. Failure hygiene. A pathological cell currently burns 40 minutes per builder because a failed notebook is never cached and every builder re-executes it. What per-cell timeout and caching policy makes a bad cell fail fast and fail once?

Method

A small benchmark harness that runs a matrix of representative kernels (Kalman filter scan, NUTS on the sargent_surico model, large dense linear algebra as a control, the jax_intro 50M-element ops) across {T4 GPU, runner CPU} x {float32, float64} x {1, 2, 3 concurrent processes}, with a jax profile captured for the pathological cases to separate kernel time from gap time. Run it on the actual CI runners via workflow_dispatch so the numbers are the numbers we live with. The two incident lectures become the first regression fixtures.

Deliverables

  1. A short findings report with the measured crossover points and the answer to each question above, checked into QuantEcon/meta.
  2. Guidance for lecture authors (a page in the style guide or manual): when to expect the GPU to help, when to pin a lecture to CPU, and the sampler/config idioms that make small models CI-safe.
  3. Standard CI configuration rolled out across the five GPU-CI lecture repos — env vars, concurrency settings, timeouts — ideally via the shared infrastructure in quantecon/actions so it lands once (related: Migrate the five native lecture CI pipelines to quantecon/actions/build-lectures #348, GitHub Actions config hardening across the five GPU-CI lecture repos #347).
  4. An audit of existing JAX lectures against the findings: which are in the losing region (small model + x64 on GPU) and should be pinned or restructured.

Out of scope

Rewriting lecture content for performance, GPU vendor comparisons beyond what instance selection requires, and anything about the mystmd/jupyter-book build engines (tracked separately on QuantEcon/lecture-python-programming#363).

Evidence trail

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions