Lab 01 — Experiment Tracking Store
Phase 26 · Lab 01 · Phase README · Warmup
The problem
You are sweeping a model over a dozen hyper-parameter configurations. Three weeks later someone
asks: "which run got 0.94 accuracy, what learning rate was it, and can you reproduce it?" If the
answer lives in a scrolled-past terminal or a filename like model_final_v2_REAL.pkl, you do not
have MLOps — you have folklore. An experiment tracking store is the fix: every training
execution becomes a queryable run with its params, its metrics (including the full
step-by-step learning curve), its output artifacts, and lineage back to the code and data
that produced it.
This is the data model and selection logic underneath MLflow, Weights & Biases, Neptune, and Comet. The UI, the database, and the network hop are theirs; the part that matters — runs grouped under experiments, metric step-history, structured search, best-run selection, and provable reproducibility — is what you build here, in pure stdlib.
What you build
| Piece | What it does | The lesson |
|---|---|---|
TrackingStore.start_run | opens a run under an experiment; ids are sequential counters | reproducibility is a property you assert, not a hope — no uuid4, no timestamps |
log_param / log_metric / log_artifact | record hyper-params, metric observations, artifacts | the three things every run carries; metrics keep step history, artifacts are content-hashed |
metric_history | full (step, value) curve per metric key | a metric is a time series, not a scalar — that is how you plot a learning curve |
hash_artifact | content-addressed artifact "storage" (sha256) | same bytes -> same hash: an unchanged model is provably unchanged |
Condition + search_runs | structured filter (metrics.accuracy > 0.9) with an operator table | how MLflow's search_runs filter string actually evaluates |
compare_runs | side-by-side param/metric diff across runs | the compare view a tracker's UI renders |
best_run(metric, mode) | pick the winner by any metric, min or max | model selection is a query, deterministic down to the tie-break |
File map
| File | Role |
|---|---|
lab.py | your implementation (fill the # TODOs) |
solution.py | reference implementation + worked example (python solution.py) |
test_lab.py | 28 tests: ids, logging, step history, artifact hashing, search, compare, best-run, reproducibility |
requirements.txt | pytest only |
Run it
pytest test_lab.py -v # your lab.py (red until you implement)
LAB_MODULE=solution pytest test_lab.py -v # the reference (green)
python solution.py # worked example
Success criteria
-
start_runhands outrun-1,run-2, ... in order; the same script twice yields identical run records (ids, params, metric history, artifact hashes, tags). -
log_metricin a loop builds ametric_historyof(step, value)pairs with auto- incrementing steps; the run's "current" metric value is the last one logged. -
log_artifactreturns a deterministic content hash; identical content always hashes the same. -
Runs are grouped by experiment;
list_runs(exp)returns only that experiment's runs. -
search_runsfilters onmetrics/params/tags/statuswith> >= < <= = != ==, ANDs multiple conditions, excludes runs missing a referenced metric, and canorder_bya metric. -
best_runselects correctly formode="max"andmode="min", excludes runs without the metric, and breaks ties by run id. -
All 28 tests pass under both
labandsolution.
How this maps to the real stack
- MLflow Tracking is the closest analogue:
mlflow.start_run(),log_param,log_metric(which really does keep a per-step history you can plot),log_artifact, andmlflow.search_runs(filter_string="metrics.accuracy > 0.9"). OurConditionis that filter string already parsed into(kind, key, op, value); real MLflow compiles the string to the same shape before evaluating it against its backing store (a SQL database or the localmlruns/directory). - Weights & Biases models the same run/metric/artifact triple with
wandb.log({...})streaming metrics by step,wandb.configfor params, andwandb.Artifactfor versioned outputs — its artifacts are content-addressed by a hash exactly likehash_artifact, which is how W&B deduplicates identical files across runs. - Run ids in production are UUIDs, not counters — but every serious tracker treats a run as immutable once finished and content-addresses artifacts precisely so a rerun is diffable. Our sequential ids make that determinism assertable in a test; the lesson (reproducibility is designed in, not discovered) is identical.
- Lineage is the tags in
start_run(tags={"git_sha": ..., "data_version": ...}). Real trackers auto-capture the git commit, the entrypoint, the environment, and (with a data-versioning tool like DVC or LakeFS) the dataset hash — so a run points back at the exact code and data. That triple — code + data + config — is the "three axes of change" the Warmup opens with, and it is what makes a model reproducible.
Limits of the miniature. No database, no concurrency, no UI, no distributed writers — a production tracking server handles thousands of concurrent runs writing metrics, which needs a real datastore and careful write batching. Our "artifact storage" is a hash of an in-memory string, not bytes in S3/GCS. Metric history here is append-in-call-order; real trackers also record a wall-clock timestamp per point (we deliberately omit it — wall clock is non-deterministic, per the track's rules).
Extensions (your own machine)
- Add
delete_run/restore_runand alifecycle_stage(active/deleted) field, matching MLflow's soft-delete. - Add a
parent_run_idso a hyper-parameter sweep is one parent run with N child runs (MLflow's nested-run model), and makesearch_runsable to scope to children of a parent. - Persist the store to a JSON file and reload it, then prove a reloaded store still round-trips a
best_runquery — the first step toward a real backing store. - Wire a real
mlflowserver in an Extensions script (guarded by an import check) and mirror the same three runs into it, to see your miniature's API next to the real one.
Interview / resume signal
"Built an MLflow-style experiment tracking store: runs grouped under experiments, each carrying params, metric step-history (learning curves, not scalars), and content-hashed artifacts, with a structured
search_runsfilter, a compare view, and deterministic best-run selection by any metric in min or max mode — reproducibility asserted via sequential run ids and content-addressed artifacts, which is the actual mechanism W&B and MLflow use to make a rerun diffable."