Running and reusing experiment grids
September 16, 2026 ยท View on GitHub
Run from the repository root with the environment used for training:
python experiments/run_grid.py --configs 'configs/*.yaml' \
--python .venv/bin/python --gpus 0,1 --workers_per_gpu 2
# TOML uses the same flat experiment configuration schema:
python experiments/run_grid.py --configs 'configs/100gaussians/*.toml' \
--trainer experiments/train_100gaussians.py --python .venv/bin/python --gpus 0
Both TOML and YAML inputs are supported. The runner checks every config before
launching children. Invalid TOML/YAML, unknown
keys (for trainers declaring DEFAULTS), nonpositive worker counts, and duplicate
or nested output directories fail the grid. Independent runner processes use a
POSIX file lock to prevent simultaneous writes to the same output directory.
An unrelated nonempty directory and directories containing source/config inputs
cannot be archived as run outputs.
Completed results are reused only when run_grid_complete.json matches all of:
- The full requested configuration, including the trainer's current defaults.
- The trainer, runner, shared config reader, and local Python sources under
lib/andparticlegan/(plus primary-trainer dependencies). - The resolved Python executable path and the saved summary's SHA-256 digest.
The manifest is written atomically after a successful child exit, a nonempty
summary.json final block, and an exact match of its recorded config. Legacy
results without a completion manifest are preserved and rerun. A source edit
during a grid prevents certification of affected runs. This fingerprint covers
local source; it does not record installed dependency versions, hardware, or
hash every generated artifact. Use a fixed environment for reproducible studies.
Before a rerun, previous output files are moved intact to
<out_dir.parent>/.run_grid_history/<out_dir.name>/<timestamp>-<id>/. The new
attempt begins in a clean directory. --force uses the same archive behavior.
Failed attempts retain their logs; a summary written by a failed child is renamed
summary.failed.json and cannot be reused. Other queued jobs continue, failures
are logged in results/failures.txt, and the overall command exits nonzero.
Custom trainers must accept --config, report the complete effective config in
their summary, and declare a DEFAULTS mapping or receive every config value
explicitly in their input. Defaults can be literal values or unpack the shared
recipe_defaults("100gaussians") / recipe_defaults("denoising") helper into
a dictionary with literal overrides. The runner reads this narrow syntax using
Python's AST parser; it never imports the trainer or evaluates arbitrary trainer
expressions. This lets the primary trainers inherit package recipe defaults
without executing their training code during validation.
Regardless of input format, the runner passes an immutable, fully resolved copy
named requested_config.yaml to the child. This YAML file remains the canonical
runner request artifact; changing the original TOML or YAML while a run is queued
does not change that run's request.
Sparse pipeline
PY=.venv/bin/python GPUS=0 WORKERS=2 experiments/sparse_pipeline.sh
BASE='coeff=0.3,total_steps=1000' experiments/sparse_pipeline.sh recipe sparse
The default stages are recipe ucd sparse discrete champion fewshot; smoke can
be requested explicitly. Generation, training, and analysis errors stop the
pipeline. Its log is results/sparse/PIPELINE.log.
Config precedence is trainer defaults, stage BASE (where applicable), group
overrides, then --base/BASE. Thus BASE='coeff=0.3' also overrides a group's
coefficient sweep. --total_steps/STEPS supplies the fallback run length; named
short/long groups keep their own length. Use BASE='total_steps=1000' to override
every group's length. The generator owns seed and out_dir; select seeds with
--seeds/SEEDS.
Nondefault BASE, seed lists, or step settings select a deterministic namespace
such as sparse__<hash> under both configs/sparse/ and results/sparse/runs/.
Default invocations keep the historical stage names. Use the generator's
--print_stage option to get the resolved namespace without writing files, and
pass that name to analyze_sparse.py --stage when analyzing manually. The
pipeline does this automatically. A generated manifest.json lists the exact
config paths for the invocation; run_grid.py --config_manifest <path> excludes
stale YAML files that may also be present in the config directory. The pipeline
passes the same manifest to analyze_sparse.py --config_manifest <path>, so
obsolete result groups are excluded too. Manifest analysis checks each summary's
full config against the current request/defaults and exits nonzero when any
listed run is missing or mismatched. Omit the manifest for historical directory
scanning when analyzing earlier studies manually.