Skip to content

Repository files navigation

FlorDB: Log-First Context Management for ML Devs

PyPI

FlorDB brings experiment tracking, provenance, and reproducibility to your ML workflow using the one thing every engineer already writes: logs. FlorDB doesn’t ask you to adopt a new UI, schema, or service. Just import it, log as you normally would, and gain full history, lineage, and replay capabilities across your training runs.

🌻 Why FlorDB?

  • Zero Code Changes to Start
    Already using print or logging inside a git repo? Import FlorDB and your .py output is captured, versioned, and queryable—no rewrite required.

  • Log-Driven Experiment Tracking
    No dashboards to configure or schemas to design. flor.log(...) writes structured metadata; flor.arg(...) turns a constant into a CLI-settable hyperparameter that is recorded with the run.

  • Hindsight Logging & Replay
    Missed a metric? Add a log after the fact and replay past runs to capture it.

  • Reproducibility Without Friction
    Every run is versioned via Git, every hyperparameter is recorded, and PyTorch checkpoints are captured and addressable by loop iteration—automatically.

  • Works With Your Stack
    Makefiles, Airflow, Slurm, HuggingFace, PyTorch—you don’t change your workflow. FlorDB fits in.

📦 Installation

pip install flordb

For contributors or bleeding-edge features:

git clone https://github.com/ucbrise/flor.git
cd flor
pip install -e .

📝 First Log in 30 Seconds

Requires a Git repository for automatic versioning.

mkdir flor_sandbox
cd flor_sandbox
git init
ipython
import flordb as flor
flor.log("message", "Hello ML World!")
message: Hello ML World!

Run committed successfully.

Retrieve logs anytime:

flor.dataframe("message")
         projid              tstamp filename   source          message
0  flor_sandbox 2025-10-13 18:13:48  ipython  forward  Hello ML World!

🪵 Already Using print and logging? Just Import

Add one import to a script you already have. Nothing else changes:

import flordb as flor          # <-- the only new line

for epoch in range(3):
    print(f"epoch {epoch} | loss: {1.0 / (epoch + 2):.4f}")

Your terminal looks exactly the same. But the run is now versioned, committed, and queryable with flor.io().

Automatic log capture: channels, naming your loops, and turning captured text into real metric columns.

🧪 Track Experiments with Zero Overhead

Adopt as much as you want. Every step buys a specific thing:

import flordb as flor

lr = flor.arg("lr", 1e-3)                     # CLI-settable, recorded with the run
batch_size = flor.arg("batch_size", 32)

for epoch in flor.loop("epoch", range(epochs)):
    for x, y in flor.loop("step", trainloader):
        ...
        flor.log("loss", loss.item())
    flor.log("val_acc", validate(net))

    torch.save({"model": net.state_dict()}, "ckpt.pth")   # mirrored to flor (rate-limited)

Change hyperparameters from the CLI:

python train.py --kwargs lr=5e-4 batch_size=64

View metrics across runs:

flor.dataframe("lr", "batch_size", "loss")
        projid                     tstamp  filename   source  epoch  step      lr batch_size    loss
0  ml_tutorial 2026-08-13 11:27:06.417615  train.py  forward      0     0  0.0005         64     0.5
1  ml_tutorial 2026-08-13 11:27:06.417615  train.py  forward      0     1  0.0005         64  0.3333
2  ml_tutorial 2026-08-13 11:27:06.417615  train.py  forward      1     0  0.0005         64  0.3333
3  ml_tutorial 2026-08-13 11:27:06.417615  train.py  forward      1     1  0.0005         64    0.25
4  ml_tutorial 2026-08-13 11:27:06.417615  train.py  forward      2     0  0.0005         64    0.25
5  ml_tutorial 2026-08-13 11:27:06.417615  train.py  forward      2     1  0.0005         64     0.2

Each named flor.loop becomes its own column, and a row carries the loops that enclosed the flor.log that produced it. loss is logged inside step, so you get one row per step, with its epoch and the run's hyperparameters attached—no JOIN needed.

Checkpoints: what gets mirrored, enrolling objects explicitly, and bounding disk use.

🔍 Hindsight Logging, or Logging After the Fact

Forgot to log gradient norms? Add the statement to the script now:

flor.log("grad_norm", ...)
python -m flordb replay --apply grad_norm

FlorDB walks the historical versions, splices your new statement into each one, restarts from the nearest checkpoint, and records the recovered values.

Replay: narrowing the work by iteration, replaying a single run, and --override.

📁 What FlorDB Writes

Everything is project-local; nothing lands in your home directory. Your first run moves you off main onto a shadow branch named flor.branch, and every auto-commit lands there—run history is versioned alongside the code that produced it (without ever touching the branch you review and merge). A teammate runs git fetch && git checkout flor.branch && python -m flordb unpack and has everyone's metrics.

Storage: the .flor/ layout, the shadow branch, and what syncs vs. what's rebuilt on demand.

📚 Publications

FlorDB is based on research from UC Berkeley’s RISE Lab continued at Arizona State University.

  • Flow with FlorDB: Incremental Context Maintenance for the Machine Learning Lifecycle (CIDR 2025)
  • The Management of Context in the ML Lifecycle (UCB Tech Report 2024)
  • Hindsight Logging for Model Training (PVLDB 2021)

🛠 License

Apache v2 License — free to use, modify, and distribute.

💡 Get Involved

FlorDB is actively developed. Contributions, issues, and real-world use cases are welcome!

make test        # full suite, including real forward runs and replays
make test-fast   # unit tests only (~1s)

Email: rolando.garcia@asu.edu
Tutorial Video: https://youtu.be/mKENSkk3S4Y