The per-epoch training fan-out only makes sense if epoch k is the same
epoch either way, and the shuffle fix alone wasn't enough: run_train_job
calls seed_everything(train.seed) at process start, so a fresh job
restarted the torch/numpy stream at epoch 1's state and drew different
flow/WGAN noise than the corresponding epoch of a single long run.
giant.config.epoch_seed derives a per-epoch seed, and the training loop
reseeds from it at the top of every epoch. Verified on a 3-epoch toy run:
the chained workflow's concatenated metrics.csv is now byte-identical to a
single `giant train --epochs 3` with the same seed (it matched only on
epoch 1 before).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>