Reproducibility¶
Every run records what you need to get back to the code and environment that produced it: the git commit and any uncommitted changes, the installed packages, the command line and the machine. This page says what is recorded, how to replay a run from it, and what it does not cover.
Nothing here needs setting up: init records it unless you pass
capture_env=False. Recording is best effort. If something cannot be read (no
git, a folder that is not a repository, unreadable package metadata), that part
is left out, with a warning when it is unexpected, and the run starts anyway.
What is recorded¶
| What | Where | Notes |
|---|---|---|
| Commit, branch, remote | run.json → git |
The remote URL is stored without any user name, password or token. |
| Uncommitted changes | files/git.patch |
git diff HEAD of tracked files; written only when there are changes, up to 10 MB. |
| Whether the commit is pushed | run.json → git.pushed |
false means no remote-tracking branch contains it. |
| Untracked files | run.json → git.untracked |
How many, never their names or contents. |
| Where the repository was found | run.json → git.source |
code_dir, script or cwd; see below. |
| Installed packages | files/requirements.txt |
pip freeze format; packages installed from git keep their commit. |
| Local packages | files/requirements.txt, files/git-<package>.patch |
The folder and git state of packages installed from a local folder; see below. |
| Command line | files/command.txt |
The Python executable and sys.argv, shell-quoted. |
| Machine | run.json → env |
Host, platform, Python version and executable, working directory, GPUs (model, memory, driver), CUDA_VISIBLE_DEVICES. |
| Config | run.json → config |
Whatever you passed to init(config=...). |
| Data | run.json → inputs |
Artifact versions used with use_artifact, with file hashes. |
All of it is shown in the viewer: open a run and look at All settings. A run whose commit is not on any remote, or that had untracked files, has those rows highlighted.
Replaying a run¶
From the run's folder (<project>/runs/<run id>/):
git clone <git.remote> code && cd code
git checkout <git.commit>
git apply ../files/git.patch # only if the file exists
python -m venv .venv && . .venv/bin/activate # or: uv venv && . .venv/bin/activate
pip install -r ../files/requirements.txt # or: uv pip install -r ../files/requirements.txt
cat ../files/command.txt # then run it with this venv's python
command.txt starts with the Python executable of the machine that ran it;
replace it with python from the new environment.
Use the same Python version as env.python, and check env.gpus and
env.platform if the numbers must match exactly. Pin the data with the
artifact versions listed in inputs.
Packages installed from a local folder¶
A package you work on next to your code is often installed from its folder
(pip install -e ../mylib). PyPI does not have that code, so requirements.txt
records the folder and, when it is a git checkout, its state:
| The folder is | requirements.txt line |
To install it again |
|---|---|---|
| a clean git checkout whose commit is on a remote | mylib @ git+https://github.com/org/mylib.git@<commit> # local: /home/me/mylib |
Nothing: pip install -r (or uv pip install -r) does it. |
| a git checkout with uncommitted changes | mylib==0.3 # local: /home/me/mylib, git <commit> (dirty), patch: git-mylib.patch |
Clone, check out the commit, git apply the patch from files/, pip install -e . (or uv pip install -e .) |
| a git checkout whose commit is not pushed | mylib==0.3 # local: /home/me/mylib, git <commit> (not pushed) |
Only on that machine, or after pushing the commit. |
| inside the run's own repository | mylib==0.3 # local: /home/me/proj, the run's repository (see run.json git) |
It comes with the run's code; its changes are in git.patch. |
| not under git | mylib==0.3 # local: /home/me/mylib, not under git |
Not possible from the run alone. |
pip install -r reads everything after # as a comment, so the file stays
installable; edit the lines that are not installable as the table says. A
package in a subfolder of its repository gets #subdirectory=<dir>, and
packages of one repository share one patch. Only the first 20 local packages
are inspected; later ones keep just their path.
Which repository is recorded¶
The repository of your training code is looked for in this order, and the first folder inside a git repository wins:
code_dir, if you pass it toinit.- The folder of the script that started the process (
python ~/proj/train.pyrecords~/projwherever you run it from). - The working directory.
git.source says which one was used. When the script's folder and the
working directory are in two different repositories, the script's is recorded
and a warning says so.
Pass code_dir when the script is not your code, because the script's folder
then tells nothing about it:
- in a notebook (the "script" is the Jupyter kernel);
- with
python -m some_packageof an installed package, or a command installed by pip; - when a launcher (SLURM, a shell script, Docker, Hydra's
hydra.job.chdir) starts the process somewhere else and the entry point lives outside your repository.
run = nb.init("llm", config=cfg, code_dir="~/src/my-trainer")
What is not covered¶
Some things are deliberately not recorded, because they could hold secrets or be very large, and others cannot be:
- Untracked files. Files you never
git added are not ingit.patch; only their number is recorded. Commit, or at least add, the files a run depends on. - Binary changes.
git.patchis a text diff; changed binary files show only as "Binary files differ". - Unpushed commits. If
git.pushedisfalse, the commit exists only on the machine that ran it. Push it before you rely on the run. - Environment variables. Never recorded: they often hold API keys and
tokens. Put settings that matter into
config. - Random seeds. Recorded only if they are in
config. - Data. Recorded only for artifacts used with
use_artifact; any other file your script reads is unknown to the run. - System libraries. The CUDA toolkit, cuDNN, conda packages and OS packages are not listed; only Python packages are. The GPU driver version is.
- Large patches. A diff over 10 MB is not written; its size is recorded
in
git.patch_bytes(or aspatch too largefor a local package).
Privacy and safety¶
Capture only reads git: it triggers no hooks, runs every git command with
core.fsmonitor off and takes diffs with --no-ext-diff --no-textconv, so a
repository's fsmonitor, external diff or textconv program never starts. The
one exception is a clean filter the repository sets up (Git LFS is the common
one), which runs as it does for any git diff, because turning it off would
make every filtered file look changed. git still refuses repositories owned by
another user (safe.directory); nothing is recorded for those.
Credentials are removed from every remote URL. What is kept can still be
private: folder paths (the working directory, Python executable and local
packages), the host name, and your uncommitted code in the patches. Pass
capture_env=False to record none of it, for example when the runs are
shared. The config and metrics are still recorded.