ML checkpoint courier — Cloudflare R2 storage and D1 metadata
Project description
r2d1
Lightweight ML checkpoint courier. Ships checkpoint folders to Cloudflare R2 and records metadata to Cloudflare D1. Pulls existing checkpoints back down before a run. Model-agnostic — works with any training code that writes files to a local directory.
pip install r2d1
How it fits into bob.py
bob_job.json (secrets + config)
│
▼
bob.py
│
├─ Fetcher.from_config(secrets)
│ └─ pull("r2://jobs/<id>/latest", dest) ← checkpoint on disk (or fresh start)
│
├─ Courier.from_config(secrets)
│ └─ watch(checkpoint_dir, job_id) ← ships new checkpoints in background
│
├─ subprocess: torchrun DDIT ← trains, writes chk_N/ + chk_N.json
│
└─ courier.flush() ← wait for final upload, then exit
DDIT (and any training code) sees only local disk. It has no knowledge of R2, D1, or any cloud infrastructure.
Sidecar convention
Training code writes two things per checkpoint, folder first, sidecar last:
./checkpoints/
├── chk_0042/ ← checkpoint folder → uploaded to R2
│ ├── checkpoint.pt
│ └── config.json
└── chk_0042.json ← sidecar (written last) → triggers upload, upserted to D1
The sidecar is the atomic signal. Writing it last guarantees the folder is complete before r2d1 touches it.
Sidecar schema
{
"name": "chk_0042",
"epoch": 42,
"timestamp": 1748123456.7,
"files": ["checkpoint.pt", "config.json"],
"metadata": {"loss": 0.043}
}
Any checkpoint format works — checkpoint.pt, model.safetensors,
HuggingFace save_pretrained() output, etc. r2d1 ships whatever is in the folder.
Fetcher — pull checkpoints from R2
from r2d1 import Fetcher
# Build from bob_job.json secrets block (also exports to os.environ)
fetcher = Fetcher.from_config(cfg["secrets"])
# Pull latest checkpoint for a job — returns found=False on fresh start, never raises
info = fetcher.pull("r2://jobs/my_run/latest", dest="/root/checkpoints")
if info.found:
print(f"resuming from epoch {info.epoch} at {info.local_dir}")
else:
print("no checkpoint — fresh start")
# Pull a specific checkpoint by name
fetcher.pull("r2://jobs/my_run/chk_0042", dest="/root/checkpoints")
# Pull any R2 prefix (e.g. a dataset stored in R2)
fetcher.pull("r2://datasets/mnist", dest="/root/data")
URI schemes
| URI | What it does |
|---|---|
r2://jobs/<id>/latest |
Find highest-epoch checkpoint (D1 → R2 fallback), download it |
r2://jobs/<id>/<name> |
Download a specific named checkpoint folder |
r2://<any/prefix> |
Download all objects under that R2 prefix |
found=False is returned (not raised) when no checkpoint exists — this is
the normal fresh-start case. Check info.found before loading weights.
Courier — ship checkpoints to R2 + D1
from r2d1 import Courier
courier = Courier.from_config(cfg["secrets"])
# Start background thread — returns immediately
courier.watch("/root/checkpoints", job_id="my_run")
# ... training runs here, writes chk_N/ + chk_N.json as it goes ...
# Wait for all uploads to finish before exiting
courier.flush(timeout=300)
Or as a fully decoupled subprocess:
python -m r2d1 watch /root/checkpoints --job-id my_run
Credentials
Credentials come from the secrets block of bob_job.json. from_config()
exports them to os.environ so boto3 and requests pick them up automatically.
For local dev, put them in a .env file (requires pip install r2d1[dotenv])
or set them directly in your shell.
Required for R2
R2D1_ACCOUNT_ID
R2D1_R2_BUCKET
R2D1_R2_ACCESS_KEY
R2D1_R2_SECRET_KEY
R2D1_R2_ENDPOINT_URL (optional — auto-constructed from ACCOUNT_ID if omitted)
Optional for D1
R2D1_API_TOKEN
R2D1_D1_DATABASE_ID
If D1 credentials are absent, r2d1 runs in R2-only mode — checkpoints are still shipped and pulled, no metadata rows are written.
D1 schema
CREATE TABLE IF NOT EXISTS checkpoints (
job_id TEXT NOT NULL,
name TEXT NOT NULL,
epoch INTEGER NOT NULL,
timestamp REAL NOT NULL, -- doubles as heartbeat
r2_prefix TEXT NOT NULL,
metadata TEXT DEFAULT '{}',
PRIMARY KEY (job_id, name)
);
Alice (or any orchestrator) can check job progress with:
SELECT epoch, timestamp FROM checkpoints
WHERE job_id = ? ORDER BY epoch DESC LIMIT 1;
CLI
# Ship checkpoints as they appear
python -m r2d1 watch ./checkpoints --job-id my_run [--poll-every 30]
# Pull from R2
python -m r2d1 pull r2://jobs/my_run/latest --dest ./checkpoints
python -m r2d1 pull r2://datasets/mnist --dest ./data
# Show discovered credentials
python -m r2d1 secrets
Repo structure
r2d1/
├── src/
│ └── r2d1/
│ ├── __init__.py # exports: Courier, Fetcher, FetchInfo
│ ├── __main__.py # CLI
│ ├── courier.py # Courier + _AsyncUploader
│ ├── fetcher.py # Fetcher + FetchInfo
│ ├── d1.py # D1Client (REST)
│ └── secrets.py # credential resolution
├── tests/
│ └── test_r2d1.py
└── pyproject.toml
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file r2d1-0.2.0.tar.gz.
File metadata
- Download URL: r2d1-0.2.0.tar.gz
- Upload date:
- Size: 12.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cffdd2d96df3a12f8edf36f672afd91175b0a33a987ecd4325ac8b2700600934
|
|
| MD5 |
cc6b0a16974a6043620761df96c315c7
|
|
| BLAKE2b-256 |
127d3627d8791e36b6311923848b6117063d934a8f92694b13f2a6368c49db04
|
Provenance
The following attestation bundles were made for r2d1-0.2.0.tar.gz:
Publisher:
publish.yml on sparsetrace/r2d1
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
r2d1-0.2.0.tar.gz -
Subject digest:
cffdd2d96df3a12f8edf36f672afd91175b0a33a987ecd4325ac8b2700600934 - Sigstore transparency entry: 1722241321
- Sigstore integration time:
-
Permalink:
sparsetrace/r2d1@97744536046b0c6326b437f5be59c9558746faf2 -
Branch / Tag:
refs/tags/v0.1.8 - Owner: https://github.com/sparsetrace
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@97744536046b0c6326b437f5be59c9558746faf2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file r2d1-0.2.0-py3-none-any.whl.
File metadata
- Download URL: r2d1-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c61ffeffbeedc1e58407f26ecc15c9bf481580ecd1c937b4fd3ebabc33510b41
|
|
| MD5 |
d3cf04d0e019a30f6dcd138dd11e7e1d
|
|
| BLAKE2b-256 |
6e0a72c0c769444bc48d7357d7a6fe7866f8e6500efafae07960bf9ef5b75cc7
|
Provenance
The following attestation bundles were made for r2d1-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on sparsetrace/r2d1
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
r2d1-0.2.0-py3-none-any.whl -
Subject digest:
c61ffeffbeedc1e58407f26ecc15c9bf481580ecd1c937b4fd3ebabc33510b41 - Sigstore transparency entry: 1722241450
- Sigstore integration time:
-
Permalink:
sparsetrace/r2d1@97744536046b0c6326b437f5be59c9558746faf2 -
Branch / Tag:
refs/tags/v0.1.8 - Owner: https://github.com/sparsetrace
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@97744536046b0c6326b437f5be59c9558746faf2 -
Trigger Event:
push
-
Statement type: