DevOpsIndex

Experiment Tracking & Model Registry

Experiment tracking is structured logging for ML: instead of a test report, you record the hyperparameters, metrics, and model artifacts of every training run so you can reproduce and compare them.


Core Concepts

Experiment  ←  named group  (e.g. "llama3-finetune-customer-support")
  └── Run   ←  one execution (lr=0.001, batch=32, val_loss=0.23, model.pt)
  └── Run
  └── Run   ←  best run → register to Model Registry as version 3

Model Registry  ←  separate from runs
  └── Model: "customer-support-llm"
        └── Version 1  (Staging)
        └── Version 2  (Staging)
        └── Version 3  (Production)  ← promoted from best run

The registry is the gate to production. A run logs training results; the registry tracks what's deployed.


MLflow

Architecture

graph LR
    TRAIN["Training script<br/>(local / K8s Job)"] -->|log params/metrics/artifacts| TRACK["MLflow Tracking Server<br/>REST API"]
    TRACK --> STORE["Artifact Store<br/>S3 / GCS / local"]
    TRACK --> DB["Metadata DB<br/>PostgreSQL / SQLite"]
    TRAIN -->|register model| REG["Model Registry<br/>(part of Tracking Server)"]
    CICD["CI/CD pipeline"] -->|query best run<br/>transition stage| REG
    SERVE["Serving<br/>vLLM / KServe"] -->|load model URI| STORE

Install on K8s

helm repo add community-charts https://community-charts.github.io/helm-charts
helm install mlflow community-charts/mlflow \
  --namespace mlflow --create-namespace \
  --set backendStore.postgres.enabled=true \
  --set artifactRoot.s3.enabled=true \
  --set artifactRoot.s3.bucket=my-mlflow-artifacts \
  --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::123:role/mlflow-s3

Logging a training run

import mlflow
import mlflow.pytorch

mlflow.set_tracking_uri("http://mlflow.mlflow.svc.cluster.local:5000")
mlflow.set_experiment("llama3-finetune-v2")

with mlflow.start_run(run_name="lr-0.001-batch-32") as run:
    # Log hyperparameters
    mlflow.log_params({
        "learning_rate": 0.001,
        "batch_size": 32,
        "epochs": 10,
        "model_name": "meta-llama/Meta-Llama-3-8B",
    })

    for epoch in range(10):
        train_loss, val_loss = train_epoch(model, data)

        # Log metrics per step
        mlflow.log_metrics({
            "train_loss": train_loss,
            "val_loss": val_loss,
        }, step=epoch)

    # Log model artifact
    mlflow.pytorch.log_model(
        model,
        artifact_path="model",
        registered_model_name="customer-support-llm",  # auto-registers
    )

    # Log any file (confusion matrix, eval report)
    mlflow.log_artifact("eval_report.json")

print(f"Run ID: {run.info.run_id}")
print(f"Artifact URI: {run.info.artifact_uri}")

Model Registry — promotion workflow

from mlflow import MlflowClient

client = MlflowClient("http://mlflow.mlflow.svc.cluster.local:5000")

# Find best run by val_loss
runs = client.search_runs(
    experiment_ids=["1"],
    filter_string="metrics.val_loss < 0.25",
    order_by=["metrics.val_loss ASC"],
    max_results=1,
)
best_run = runs[0]

# Register the best run's model
model_version = client.create_model_version(
    name="customer-support-llm",
    source=f"{best_run.info.artifact_uri}/model",
    run_id=best_run.info.run_id,
    description=f"Trained on {date.today()}, val_loss={best_run.data.metrics['val_loss']:.4f}",
)

# Transition to Staging
client.transition_model_version_stage(
    name="customer-support-llm",
    version=model_version.version,
    stage="Staging",        # None → Staging → Production → Archived
    archive_existing_versions=False,
)

# After validation in staging, promote to Production
client.transition_model_version_stage(
    name="customer-support-llm",
    version=model_version.version,
    stage="Production",
    archive_existing_versions=True,  # archive old production version
)

Load a production model in serving

# Always load by stage alias, not by version number
# This way promoting to Production automatically updates the serving code
model = mlflow.pytorch.load_model(
    "models:/customer-support-llm/Production"
)

Weights & Biases (W&B)

W&B is the SaaS alternative to MLflow — richer visualisation, collaborative, zero-infra.

import wandb

wandb.init(
    project="llama3-finetune",
    name="lr-0.001-batch-32",
    config={
        "learning_rate": 0.001,
        "batch_size": 32,
        "model": "meta-llama/Meta-Llama-3-8B",
    }
)

for epoch in range(10):
    train_loss, val_loss = train_epoch(model, data)
    wandb.log({"train_loss": train_loss, "val_loss": val_loss}, step=epoch)

# Save model artifact
artifact = wandb.Artifact("customer-support-llm", type="model")
artifact.add_file("model.pt")
wandb.log_artifact(artifact)

wandb.finish()

W&B Sweeps — hyperparameter search

W&B Sweeps run parallel training jobs testing different hyperparameter combinations (grid, random, or Bayesian search):

# sweep.yaml
program: train.py
method: bayes          # bayesian optimization (smarter than random)
metric:
  name: val_loss
  goal: minimize
parameters:
  learning_rate:
    min: 0.0001
    max: 0.01
  batch_size:
    values: [16, 32, 64]
  dropout:
    values: [0.1, 0.2, 0.3]
# Create sweep
wandb sweep sweep.yaml   # returns sweep ID

# Launch N agents (each runs one hyperparameter combination)
wandb agent <sweep-id> --count 10

Each agent runs train.py, W&B assigns it different hyperparameters, logs results, and updates the Bayesian model to suggest better parameters for the next run.


MLflow vs W&B

MLflow W&B
Hosting Self-hosted (K8s) SaaS (wandb.ai)
Cost Free (infra only) Free tier, paid for teams
Model registry Built-in Built-in
Sweep / HPO Optuna integration Native sweeps (Bayesian)
Visualisation Basic Rich, collaborative
Integration Any Python Any Python
Best for On-prem/self-hosted teams Teams wanting zero infra

W&B Model Registry

W&B has a separate Model Registry (different from the Artifacts tab) with staging/production aliases:

import wandb

# 1. Log model artifact during training
run = wandb.init(project="my-project")
artifact = wandb.Artifact("customer-support-llm", type="model")
artifact.add_file("model.pt")
artifact.add_file("tokenizer/")
run.log_artifact(artifact)
wandb.finish()

# 2. Link artifact to the Model Registry with an alias
api = wandb.Api()
artifact = api.artifact("my-org/my-project/customer-support-llm:v3")

# Link to registry collection
artifact.link("my-org/model-registry/customer-support-llm",
              aliases=["staging"])   # aliases: staging, production, v1.2.3

# 3. Promote to production (remove staging alias, add production)
artifact = api.artifact("my-org/model-registry/customer-support-llm:staging")
artifact.aliases = ["production"]   # replaces all aliases
artifact.save()

# 4. Fetch production model in serving code
production = api.artifact("my-org/model-registry/customer-support-llm:production")
production.download(root="./model")  # downloads to local dir

W&B Registry vs MLflow Registry comparison:

W&B Model Registry MLflow Model Registry
Stage names Custom aliases (any string) Fixed: Staging/Production/Archived
Webhooks Yes — trigger CI/CD on alias change No (use MLflow plugins)
Access control RBAC at collection level Per-model permissions
Cross-project Yes (central registry across all projects) Per-MLflow-server only
Lineage Full run → artifact → registry trace Run → model version
# W&B CLI: list all versions in registry
wandb artifact ls my-org/model-registry/customer-support-llm

# Trigger deploy on alias promotion (webhook → GitHub Actions)
# W&B → Settings → Webhooks → add endpoint
# Payload includes: artifact name, alias added/removed, version

Model Artifact Versioning Best Practices

✅ Always log the git commit hash as a param (reproducibility)
✅ Log the training data version/hash (know exactly what data produced this model)
✅ Log eval metrics before registering (never register a run without validation)
✅ Use stage aliases (Production/Staging) not version numbers in serving code
✅ Archive old Production version when promoting new one
❌ Never store model weights in git — use the artifact store
❌ Never hardcode a model version number in serving — use stage alias