Skip to content

Train progress bar#84

Open
maja-jablonska wants to merge 17 commits into
andycasey:masterfrom
maja-jablonska:train-progress-bar
Open

Train progress bar#84
maja-jablonska wants to merge 17 commits into
andycasey:masterfrom
maja-jablonska:train-progress-bar

Conversation

@maja-jablonska

Copy link
Copy Markdown

No description provided.

maja-jablonska and others added 17 commits June 5, 2026 16:25
Rewrite the training/test steps, vectorizer, continuum and model code to
use jax.numpy and jax-based autodiff/optimization (jax.vmap, jax.jacfwd,
jaxopt) in place of the numpy/scipy + multiprocessing implementation.

- Enable 64-bit JAX globally and shim jax.tree_map for jaxopt 0.8.3
- Replace hand-written label-vector derivative with jax.jacfwd
- numpy 2.0 compat (RankWarning, collections.abc.Iterable)
- Add jax/jaxlib/jaxopt deps; pytest as a test extra
- Add parity tests against frozen golden numpy/scipy outputs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fit pixels in fixed-size, pad-aligned batches instead of one fused
vmap. This drives a tqdm progress bar (the single fused call is
opaque), compiles the optimizer once and reuses it across batches, and
bounds peak memory. Pixels are independent so results are identical.

Keep theta/s2 as JAX arrays end-to-end (block_until_ready instead of
np.asarray, jnp.concatenate, single tolist() for metadata). Round-trip
now preserves them as JAX arrays; update the test contract accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The unregularized, unbounded pixel objective is a convex quadratic, so
its minimum is the normal-equations solution -- running L-BFGS on it is
wasted work (verified to land on the same point to ~1e-13). Add
make_pixel_closed_form() and route the default reg=0/no-bounds case
through it, ~3x faster at P=4200 with identical results. Censored
coefficients are pinned to zero via a unit diagonal on the Gram matrix.

Also JIT the L-BFGS init computation (one fused kernel, built only when
the optimizer runs) and block per batch only when the progress bar is
shown so batches can overlap otherwise. Add a closed-form-vs-L-BFGS
parity test covering censoring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scripts/sweep_cannon.py: k-fold cross-validation over a grid of label
sets, polynomial orders, regularization strengths and censoring
schemes. Reports per-label bias/scatter, median r_chi_sq and convex-hull
coverage, writes a tidy CSV, and (optionally) logs one offline W&B run
per grid point -- config + scalar metrics + residual histograms + a
per-run one-to-one spread figure -- plus a sweep-level summary run with
a results table and a spread-vs-parameters bar chart.

scripts/sweep.pbs / sync_wandb.pbs: run the sweep offline on a compute
node, then spawn a copyq (data-mover) job that `wandb sync`s the offline
runs to the cloud. Interpreter is selected via PYTHON_BIN.

.gitignore: ignore wandb/ run data and sweep_*results.csv.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scripts/train_cannon.py loads an APOGEE parquet table, assembles the
dispersion/flux/ivar arrays, continuum-normalizes, splits into
train/validation (dropping non-finite labels), fits a polynomial
CannonModel, runs the test step, and writes a one-to-one figure,
a predictions CSV, and optionally the trained model. Parametrized via a
CLI with a --demo mode on the bundled golden data. Device is left to
JAX_PLATFORMS rather than forced to cpu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scripts/run_sweep.py loads + continuum-normalizes the spectra via the
train_cannon helpers, drops non-finite-label stars across the union of
swept labels, then cross-validates a grid of label sets / orders /
regularizations through sweep_cannon.sweep (offline W&B per grid point).
CLI with nested-label-set defaults and a --demo mode. sweep.pbs now runs
this driver instead of the bare sweep_cannon demo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
run_sweep now constructs the sweep grid from a fixed core (--base, default
teff/logg/fe_h/mg_h), one or more age columns (--age-cols, default
age_Dnu,age_L -- each a base variant so the two ages can be compared),
an optional mass column, and a list of abundances, combined via
--label-set-mode {one-at-a-time,cumulative,minimal}. Label sets that
reference a column missing from the table are skipped with a warning, so
e.g. an absent age_Dnu no longer crashes the run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- continuum.normalize: replace in-place masked assignment with jnp.where so
  JAX-array inputs no longer raise on immutable item assignment
- model.train/test: drive progress with jax-tqdm via on-device lax.scan over
  batches (vmap within each batch); train keeps its prior batching, test gains
  a progressbar arg. Numerically identical to the previous vmap path.
- continuum.normalize: add a plain tqdm bar over the host-side per-star loop
- add end-to-end JAX test covering normalize -> build -> train -> test on a
  tiny synthetic sample
- declare jax-tqdm and tqdm in install_requires

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the host-side per-star loop in sines_and_cosines with a single
jax.jit + jax.vmap solve (_continuum_amplitudes) that fits every star at
once per region; the design matrices are shared across stars so this is a
clean batch (recompiled once per region shape). Aggregate the out-of-region
pixel warning into one message and move the optional tqdm bar to iterate over
regions. Continuum values match the previous implementation to 1e-10.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Training now optionally accounts for uncertainties on the training-set
labels. Passing `training_set_label_err` (per-label 1-sigma values) makes
`train()` propagate the label errors into the per-pixel weights and refine
them with iteratively reweighted least squares (`n_irls` passes), so stars
with uncertain labels are down-weighted. Omitting it reproduces the exact
-label fit bit-for-bit.

- fitting.py: add make_pixel_closed_form_eiv / make_pixel_fitter_eiv and a
  shared _label_variance_term (first-order propagation g^T Sigma g folded
  into ivar_eff = ivar / (1 + ivar * v_label)).
- model.py: store/validate training_set_label_err (serialized; old models
  read back as None), build the label Jacobian + scaled variances once in
  train(), and select the EIV fitters.
- model.py: make the default batch_size order-aware -- cap it to a memory
  budget so higher polynomial orders shrink the batch automatically instead
  of blowing up peak memory.
- tests: add test_label_errors.py (no-regression, finite, regularized,
  down-weighting mechanism, round-trip, validation).
- notebooks/start_with_label_errors.ipynb: copy of start.ipynb showing how
  to assemble per-label errors and train with them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eport chi2

A driver that adds the data knobs (S/N cutoff and arbitrary pandas-query row
filters) on top of the existing label-set/order/regularization sweep, and for
each grid point trains, validates, then:

- reports the recovered spread (per-label bias/scatter/RMSE + mean_scatter)
  and writes a one-to-one spread figure;
- reconstructs the validation spectra by forward-modelling the recovered
  (and reference) labels through the trained model;
- plots a few observed-vs-reconstructed spectra spanning the chi2 range;
- reports reconstruction reduced chi2 at both recovered and reference labels
  alongside the test-step chi2.

Results go to experiment_results.csv plus per-config figures. Reuses the
load/normalize/split helpers and spread_figure; optional --label-err trains
with per-label uncertainties. Includes a self-contained --demo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test step was already batched over stars, but two things made it OOM the
device on large models:

- the default batch_size ignored the model size, so a model with many pixels /
  labels used a batch far larger than the device could hold. Cap it to a memory
  budget (batch_size * per_star, per_star ~ (P, L) Jacobian + (P, T) design),
  shrinking it automatically; an explicit batch_size still wins.
- the per-star model flux (P,) returned by the spectrum fitter is not part of
  test()'s output, yet lax.scan accumulated it across batches -- wasting device
  memory of order the whole validation set (S, P). Drop it inside the scan body.

Results are unchanged (stars are fit independently; verified batch-size
independent). Full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ipts

- model.py: drop the default test working-set budget from 512 to 256 MiB so the
  memory-aware test batch_size is more conservative out of the box.
- train_cannon.py / experiment_cannon.py: add a --test-batch-size knob threaded
  into model.test(...), so the test batch can be hand-lowered when the device
  OOMs (overrides the auto default).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y on GPU

Previously every train()/test() call built fresh @jax.jit closures with the
design matrix and trained theta baked in as compile-time constants, so a
hyper-parameter sweep recompiled the XLA programs for every fold of every
grid point -- on a GPU the compiles dominate wall-clock for a sequential
sweep.

- Hoist the train/test lax.scan runners to module level, memoize them per
  (fitter, progress bar), and pass everything that varies between calls
  (design matrix, theta/s2/fiducials/scales, data batches) as arguments, so
  same-shape calls hit JAX's jit cache: all folds and all regularization
  strengths of a grid point now share one compiled program.
- Memoize the per-pixel fitter factories and key the per-spectrum core on the
  vectorizer's terms so repeated factory calls return the same function object.
- Move the initial-theta stack to a module-level jitted function.
- cross_validate: one vectorizer for all folds and no per-fold tqdm (the host
  callbacks would be baked into the scan and force recompiles).
- run_sweep: enable JAX's persistent on-disk compilation cache (programs now
  contain no array constants, so identical shapes hit it across processes and
  restarted jobs); add --jax-cache-dir.
- Add scripts/sweep_gpu.pbs: single-GPU gpuvolta job that runs the sweep
  sequentially (each grid point already saturates the device via vmap), with
  a backend assertion, no-prealloc, and the shared compilation cache.

Demo sweep: scan compiles 48 -> 24 (regs now share programs), identical
results; cross-process warm start halves a small train step on CPU.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pass --mass-col= (empty) so build_label_sets produces no base+mass
variants; the swept label sets are base(+age) plus abundance additions only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
load_spectra now derives a <x>_fe = raw_<x>_h - raw_fe_h column for every
raw [X/H] abundance except iron itself (never overwriting existing columns),
and the sweep defaults use them: the base set carries mg_fe and the swept
abundances are ce/ca/si/ni/mn/al/c/n over Fe. raw_fe_h itself stays [Fe/H].

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add quality_mask: stars with spectrum_flags != 0 or any warn_* column set
to True are dropped. run_sweep applies it right after loading -- before
continuum normalization and training -- and aborts if the cuts reject every
star. Missing columns skip the corresponding cut with a warning; NaN warn
values count as not-set, NaN spectrum_flags as not-clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant