Skip to main content

Lightweight ML experiment tracker — Cloudflare R2 checkpoints + D1 metrics

Project description

r2d1

Lightweight ML experiment tracker using Cloudflare R2 (checkpoints) + D1 (metrics & metadata).

  • Agnostic — ships whatever files your model produces. Zero opinion on format.
  • Works with PyTorch, JAX, or anything else
  • Runs anywhere — local, Colab, vast.ai, any GPU server
  • Async checkpoint uploads — GPU keeps training while files ship to R2
pip install r2d1

How it works

Your model                        r2d1
----------                        ----
model.safetensors  ──────────→   ships to R2
config.json        ──────────→   jobs/job_3/epoch_42/
run.log            ──────────→

loss, accuracy, duration  ──→   logs to D1  (live feed for you, Alice, team)

D1 always gets: epoch, loss, accuracy, duration_sec, timestamp.
R2 gets: whatever files you put in epoch.files. r2d1 never opens them.


Setup

from r2d1 import Tracker, EpochLoop

tracker = Tracker(
    account_id     = "your_cloudflare_account_id",
    api_token      = "your_cloudflare_api_token",   # Token value from R2 API token
    d1_database_id = "your_d1_database_id",
    r2_bucket      = "your_bucket_name",
    r2_access_key  = "your_r2_access_key",
    r2_secret_key  = "your_r2_secret_key",
)
# D1 tables created automatically on first run

Usage

tqdm-style loop

job = tracker.start_job("dit_run1", dataset_key="hf://datasets/imagenet-1k")

try:
    for epoch in EpochLoop(range(400), job=job):

        loss = train_step(...)      # your JIT'd / multi-GPU code — untouched

        epoch.loss  = float(loss)
        epoch.files = {             # your model packages its own checkpoint
            "model.safetensors": model.to_safetensors_bytes(),
            "config.json":       model.to_config_bytes(),
            "run.log":           logger.flush_bytes(),
        }
        # ↑ files upload async to R2, loss logs to D1 — both happen automatically

    job.complete()

except Exception:
    job.interrupt()   # D1 status → 'interrupted', Alice can restart
    raise

Resume after interruption

job = tracker.resume_job(job_id=3)

files = job.load_latest()   # downloads all files from last checkpoint
# files["model.safetensors"], files["config.json"], etc.

model  = MyModel.from_safetensors_bytes(files["model.safetensors"],
                                        files["config.json"])
logger = Logger.from_bytes(files["run.log"])

for epoch in EpochLoop(range(400), job=job, start_epoch=last_epoch + 1):
    ...

Decorator style

@tracker.job(name="dit_run1", dataset_key="hf://datasets/imagenet-1k")
def train(job):
    for epoch in EpochLoop(range(400), job=job):
        loss = train_step(...)
        epoch.loss  = float(loss)
        epoch.files = model.checkpoint()

train()   # start/interrupt/complete handled automatically

EpochLoop options

EpochLoop(
    range(400),
    job              = job,
    checkpoint_every = 10,    # ship files every 10 epochs (default: 1)
    log_every        = 1,     # log to D1 every epoch (default: 1)
    start_epoch      = 50,    # skip first 50 epochs — for resuming
    async_checkpoint = True,  # upload in background (default: True)
)

R2 layout

r2://your-bucket/
└── jobs/
    └── job_3/
        ├── epoch_0/
        │   ├── model.safetensors
        │   ├── config.json
        │   └── run.log
        ├── epoch_10/
        │   └── ...
        └── epoch_42/
            └── ...

D1 schema (auto-created)

jobsid, name, dataset_key, status, last_checkpoint_prefix, submitted_at, updated_at
epochsjob_id, epoch, loss, accuracy, duration_sec, logged_at


Files

r2d1/
├── r2d1/
│   ├── __init__.py   # exports Tracker, Job, EpochLoop
│   ├── tracker.py    # Tracker + Job — R2 upload, D1 logging
│   └── loop.py       # EpochLoop + @tracker.job decorator
├── setup.py
└── README.md

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

r2d1-0.1.3.tar.gz (9.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

r2d1-0.1.3-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

File details

Details for the file r2d1-0.1.3.tar.gz.

File metadata

  • Download URL: r2d1-0.1.3.tar.gz
  • Upload date:
  • Size: 9.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for r2d1-0.1.3.tar.gz
Algorithm Hash digest
SHA256 b9d62869394a58dac2ca6d228eddb94d433bff44e40290ee89de1cdebc21e2ce
MD5 80d8c8a79eb22f765abb95e8b39d8037
BLAKE2b-256 592d1aea30d1da86de717c0047a862ad488e7b3ccb92afa128e4a3255d6442ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for r2d1-0.1.3.tar.gz:

Publisher: publish.yml on sparsetrace/r2d1

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file r2d1-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: r2d1-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 9.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for r2d1-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 91588e880f89248742dee760d38c2506b1dc2d0939e0b3a99524cf9e5e58bd39
MD5 849d2b1d43de90e3920bebc57652d743
BLAKE2b-256 dccbfae6631456e89ef11d76e10fc8cab098068824f4c7dcb2ee6a94e8028218

See more details on using hashes here.

Provenance

The following attestation bundles were made for r2d1-0.1.3-py3-none-any.whl:

Publisher: publish.yml on sparsetrace/r2d1

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page