Make a run reproducible¶
Reproducibility is available, not automatic: some of what varies is seeded by default and some deliberately is not — the ported algorithms seed neither their replay draw nor their Fisher sample, faithfully to the originals.
Set the seeds that are not set for you:
run: {seed: 0}
algorithm:
name: ppo
params:
replay_seed: 0 # the hindsight-replay draw
fisher_seed: 0 # the EWC Fisher sample
policy:
id: skyfall_crl.train.backends.policy:language_policy
kwargs: {model: ./sft-ckpt, seed: 0}
run.seed reaches the environment and nothing else. Everything else the seed should reach is
written where it belongs — the policy's kwargs, the algorithm's params — and in a sweep that
is what ${seed} does: substituted wherever it appears, so every cell's seed reaches whatever
the document says it reaches. A misplaced key is an error, not a no-op: a document that puts
policy: at the top level is refused at load, naming the key.
Prove it, don't assume it. Tutorial L5 does exactly this, gate-verified: the same document run twice produces 600 byte-identical trace rows. The equivalent check for your own run is two runs into two paths and a comparison of the deterministic JSON:
skyfall-crl eval --traces first.jsonl --format json --output a.json
skyfall-crl eval --traces second.jsonl --format json --output b.json
diff a.json b.json
The JSON carries no timestamp and no absolute path, so identical runs produce byte-identical
files — an empty diff is the proof.
What can still differ. A reward component that reads a clock scores by observed_at when the
row carries one — traces recorded without it replay against now. Unseeded inference differs
every time, which is why a scored serve names --seed. And identical results across seeds is
its own warning sign: if every seeded run matches, the seed reached
nothing.