MLFlow

We show how LaminDB can be integrated with MLflow to track the training process and associate datasets & parameters with models.

# !pip install 'lamindb[jupyter]' torchvision lightning wandb
!lamin init --storage ./lamin-mlops
import lamindb as ln
import mlflow
import lightning

from torch import utils
from torchvision.datasets import MNIST
from torchvision.transforms import ToTensor
from autoencoder import LitAutoEncoder

ln.track()
Hide code cell output
→ connected lamindb: anonymous/lamin-mlops
→ created Transform('8bRjGzrd96Vs0000'), started new Run('GJz3fAng...') at 2025-06-03 11:34:13 UTC
→ notebook imports: autoencoder lamindb==1.6.0 lightning==2.5.1.post0 mlflow-skinny==2.22.0 mlflow==2.22.0 torch==2.7.0 torchvision==0.22.0
• recommendation: to identify the notebook across renames, pass the uid: ln.track("8bRjGzrd96Vs")

Define a model

We use a basic PyTorch Lightning autoencoder as an example model.

Code of LitAutoEncoder
Simple autoencoder model
import torch
import lightning

from torch import optim, nn


class LitAutoEncoder(lightning.LightningModule):
    def __init__(self, hidden_size: int, bottleneck_size: int) -> None:
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Linear(28 * 28, hidden_size),
            nn.ReLU(),
            nn.Linear(hidden_size, bottleneck_size),
        )
        self.decoder = nn.Sequential(
            nn.Linear(bottleneck_size, hidden_size),
            nn.ReLU(),
            nn.Linear(hidden_size, 28 * 28),
        )
        self.save_hyperparameters()

    def training_step(
        self, batch: tuple[torch.Tensor, torch.Tensor], batch_idx: int
    ) -> torch.Tensor:
        x, y = batch
        x = x.view(x.size(0), -1)
        z = self.encoder(x)
        x_hat = self.decoder(z)
        loss = nn.functional.mse_loss(x_hat, x)
        self.log("train_loss", loss)
        return loss

    def configure_optimizers(self) -> optim.Optimizer:
        optimizer = optim.Adam(self.parameters(), lr=1e-3)
        return optimizer

Query & download the MNIST dataset

We saved the MNIST dataset in curation notebook which now shows up in the Artifact registry:

ln.Artifact.filter(kind="dataset").df()
Hide code cell output
uid key description suffix kind otype size hash n_files n_observations _hash_type _key_is_virtual _overwrite_versions space_id storage_id schema_id version is_latest run_id created_at created_by_id _aux branch_id
id
1 5M0828i4DPDrHSub0000 testdata/mnist None dataset None 54950048 amFx_vXqnUtJr0kmxxWK2Q 4 None md5-d True True 1 1 None None True 1 2025-06-03 11:33:32.756000+00:00 1 None 1

You can also find it on lamin.ai if you were connected your instance.

instance view

Let’s get the dataset:

artifact = ln.Artifact.get(key="testdata/mnist")
artifact
Hide code cell output
Artifact(uid='5M0828i4DPDrHSub0000', is_latest=True, key='testdata/mnist', suffix='', kind='dataset', size=54950048, hash='amFx_vXqnUtJr0kmxxWK2Q', n_files=4, branch_id=1, space_id=1, storage_id=1, run_id=1, created_by_id=1, created_at=2025-06-03 11:33:32 UTC)

And download it to a local cache:

path = artifact.cache()
path
Hide code cell output
PosixUPath('/home/runner/work/lamin-mlops/lamin-mlops/docs/lamin-mlops/.lamindb/5M0828i4DPDrHSub')

Create a PyTorch-compatible dataset:

dataset = MNIST(path.as_posix(), transform=ToTensor())
dataset
Hide code cell output
Dataset MNIST
    Number of datapoints: 60000
    Root location: /home/runner/work/lamin-mlops/lamin-mlops/docs/lamin-mlops/.lamindb/5M0828i4DPDrHSub
    Split: Train
    StandardTransform
Transform: ToTensor()

Monitor training with MLflow

Train our example model and track the training progress with MLflow.

mlflow.pytorch.autolog()

MODEL_CONFIG = {"hidden_size": 32, "bottleneck_size": 16, "batch_size": 32}

# Start MLflow run
with mlflow.start_run() as run:
    train_dataset = MNIST(
        root="./data", train=True, download=True, transform=ToTensor()
    )
    train_loader = utils.data.DataLoader(
        train_dataset, batch_size=MODEL_CONFIG["batch_size"]
    )

    # Initialize model
    autoencoder = LitAutoEncoder(
        MODEL_CONFIG["hidden_size"], MODEL_CONFIG["bottleneck_size"]
    )

    # Create checkpoint callback
    from lightning.pytorch.callbacks import ModelCheckpoint

    checkpoint_callback = ModelCheckpoint(
        dirpath="model_checkpoints",
        filename=f"{run.info.run_id}_last_epoch",
        save_top_k=1,
        monitor="train_loss",
    )

    # Train model
    trainer = lightning.Trainer(
        accelerator="cpu",
        limit_train_batches=3,
        max_epochs=2,
        callbacks=[checkpoint_callback],
    )

    trainer.fit(model=autoencoder, train_dataloaders=train_loader)

    # Get run information
    run_id = run.info.run_id
    metrics = mlflow.get_run(run_id).data.metrics
    params = mlflow.get_run(run_id).data.params

    # Access model artifacts path
    model_uri = f"runs:/{run_id}/model"
    artifacts_path = run.info.artifact_uri
Hide code cell output
2025/06/03 11:34:14 WARNING mlflow.utils.autologging_utils: MLflow pytorch autologging is known to be compatible with 1.9.0 <= torch <= 2.6.0, but the installed version is 2.7.0+cu126. If you encounter errors during autologging, try upgrading / downgrading torch to a compatible version, or try upgrading MLflow.
  0%|          | 0.00/9.91M [00:00<?, ?B/s]
  1%|          | 65.5k/9.91M [00:00<00:17, 573kB/s]
  2%|▏         | 229k/9.91M [00:00<00:09, 1.07MB/s]
 10%|▉         | 983k/9.91M [00:00<00:02, 3.54MB/s]
 41%|████      | 4.03M/9.91M [00:00<00:00, 12.5MB/s]
100%|██████████| 9.91M/9.91M [00:00<00:00, 18.6MB/s]

  0%|          | 0.00/28.9k [00:00<?, ?B/s]
100%|██████████| 28.9k/28.9k [00:00<00:00, 506kB/s]

  0%|          | 0.00/1.65M [00:00<?, ?B/s]
  6%|▌         | 98.3k/1.65M [00:00<00:01, 854kB/s]
 24%|██▍       | 393k/1.65M [00:00<00:00, 1.85MB/s]
 91%|█████████▏| 1.51M/1.65M [00:00<00:00, 5.39MB/s]
100%|██████████| 1.65M/1.65M [00:00<00:00, 4.72MB/s]

  0%|          | 0.00/4.54k [00:00<?, ?B/s]
100%|██████████| 4.54k/4.54k [00:00<00:00, 10.7MB/s]
INFO:pytorch_lightning.utilities.rank_zero:GPU available: False, used: False
INFO:pytorch_lightning.utilities.rank_zero:TPU available: False, using: 0 TPU cores
INFO:pytorch_lightning.utilities.rank_zero:HPU available: False, using: 0 HPUs
/opt/hostedtoolcache/Python/3.13.3/x64/lib/python3.13/site-packages/lightning/pytorch/trainer/connectors/logger_connector/logger_connector.py:76: Starting from v1.9.0, `tensorboardX` has been removed as a dependency of the `lightning.pytorch` package, due to potential conflicts with other packages in the ML ecosystem. For this reason, `logger=True` will use `CSVLogger` as the default logger, unless the `tensorboard` or `tensorboardX` packages are found. Please `pip install lightning[extra]` or one of them to enable TensorBoard support by default
2025/06/03 11:34:17 WARNING mlflow.utils.autologging_utils: MLflow autologging encountered a warning: "/opt/hostedtoolcache/Python/3.13.3/x64/lib/python3.13/site-packages/mlflow/pytorch/_lightning_autolog.py:465: UserWarning: Autologging is known to be compatible with pytorch-lightning versions between 1.9.0 and 2.5.1 and may not succeed with packages outside this range."
/opt/hostedtoolcache/Python/3.13.3/x64/lib/python3.13/site-packages/lightning/pytorch/callbacks/model_checkpoint.py:654: Checkpoint directory /home/runner/work/lamin-mlops/lamin-mlops/docs/model_checkpoints exists and is not empty.

  | Name    | Type       | Params | Mode 
-----------------------------------------------
0 | encoder | Sequential | 25.6 K | train
1 | decoder | Sequential | 26.4 K | train
-----------------------------------------------
52.1 K    Trainable params
0         Non-trainable params
52.1 K    Total params
0.208     Total estimated model params size (MB)
8         Modules in train mode
0         Modules in eval mode
/opt/hostedtoolcache/Python/3.13.3/x64/lib/python3.13/site-packages/lightning/pytorch/trainer/connectors/data_connector.py:425: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=3` in the `DataLoader` to improve performance.
/opt/hostedtoolcache/Python/3.13.3/x64/lib/python3.13/site-packages/lightning/pytorch/loops/fit_loop.py:310: The number of training batches (3) is smaller than the logging interval Trainer(log_every_n_steps=50). Set a lower value for log_every_n_steps if you want to see logs for the training epoch.
Training: |          | 0/? [00:00<?, ?it/s]
Training:   0%|          | 0/3 [00:00<?, ?it/s]
Epoch 0:   0%|          | 0/3 [00:00<?, ?it/s] 
Epoch 0:  33%|███▎      | 1/3 [00:00<00:00, 48.62it/s]
Epoch 0:  33%|███▎      | 1/3 [00:00<00:00, 47.03it/s, v_num=0]
Epoch 0:  67%|██████▋   | 2/3 [00:00<00:00, 70.85it/s, v_num=0]
Epoch 0:  67%|██████▋   | 2/3 [00:00<00:00, 69.51it/s, v_num=0]
Epoch 0: 100%|██████████| 3/3 [00:00<00:00, 85.18it/s, v_num=0]
Epoch 0: 100%|██████████| 3/3 [00:00<00:00, 83.86it/s, v_num=0]
Epoch 0: 100%|██████████| 3/3 [00:00<00:00, 82.18it/s, v_num=0]
2025/06/03 11:34:17 WARNING mlflow.utils.checkpoint_utils: Checkpoint logging is skipped, because checkpoint 'save_best_only' config is True, it requires to compare the monitored metric value, but the provided monitored metric value is not available.
Epoch 0:   0%|          | 0/3 [00:00<?, ?it/s, v_num=0]        
Epoch 1:   0%|          | 0/3 [00:00<?, ?it/s, v_num=0]
Epoch 1:  33%|███▎      | 1/3 [00:00<00:00, 138.28it/s, v_num=0]
Epoch 1:  33%|███▎      | 1/3 [00:00<00:00, 128.97it/s, v_num=0]
Epoch 1:  67%|██████▋   | 2/3 [00:00<00:00, 140.10it/s, v_num=0]
Epoch 1:  67%|██████▋   | 2/3 [00:00<00:00, 134.77it/s, v_num=0]
Epoch 1: 100%|██████████| 3/3 [00:00<00:00, 140.37it/s, v_num=0]
Epoch 1: 100%|██████████| 3/3 [00:00<00:00, 137.01it/s, v_num=0]
Epoch 1: 100%|██████████| 3/3 [00:00<00:00, 132.83it/s, v_num=0]
2025/06/03 11:34:17 WARNING mlflow.utils.checkpoint_utils: Checkpoint logging is skipped, because checkpoint 'save_best_only' config is True, it requires to compare the monitored metric value, but the provided monitored metric value is not available.
INFO:pytorch_lightning.utilities.rank_zero:`Trainer.fit` stopped: `max_epochs=2` reached.
Epoch 1: 100%|██████████| 3/3 [00:00<00:00, 101.40it/s, v_num=0]

2025/06/03 11:34:24 WARNING mlflow.models.model: Model logged without a signature and input example. Please set `input_example` parameter when logging the model to auto infer the model signature.

See the training progress in the mlflow UI:

MLFlow training UI

Save model in LaminDB

# save checkpoint as a model in LaminDB
artifact = ln.Artifact(
    f"model_checkpoints/{run_id}_last_epoch.ckpt",
    key="testmodels/mlflow/litautoencoder.ckpt",  # is automatically versioned
    type="model",
).save()

# create a label with the mlflow experiment name
mlflow_run_name = mlflow.get_run(run_id).data.tags.get(
    "mlflow.runName", f"run_{run_id}"
)
experiment_label = ln.ULabel(
    name=mlflow_run_name, description="mlflow experiment name"
).save()

# annotate the model Artifact
artifact.ulabels.add(experiment_label)

# define the associated model hyperparameters in ln.Param
for k, v in MODEL_CONFIG.items():
    ln.Param(name=k, dtype=type(v).__name__).save()
artifact.params.add_values(MODEL_CONFIG)

# look at Artifact annotations
artifact.describe()
artifact.params
Hide code cell output
! `type` will be removed soon, please use `kind`
! calling anonymously, will miss private instances
→ returning existing Feature record with same name: 'hidden_size'
→ returning existing Feature record with same name: 'bottleneck_size'
→ returning existing Feature record with same name: 'batch_size'
/tmp/ipykernel_3331/2236147127.py:22: FutureWarning: Use features instead of params, params will be removed in the future.
  artifact.params.add_values(MODEL_CONFIG)
Artifact .ckpt
├── General
│   ├── .uid = '4DSByE7nnqos4Mkf0000'
│   ├── .key = 'testmodels/mlflow/litautoencoder.ckpt'
│   ├── .size = 636736
│   ├── .hash = 'j3PiRncPY0aSNJh7BSUBhA'
│   ├── .path = /home/runner/work/lamin-mlops/lamin-mlops/docs/lamin-mlops/.lamindb/4DSByE7nnqos4Mkf0000.ckpt
│   ├── .created_by = anonymous
│   ├── .created_at = 2025-06-03 11:34:25
│   └── .transform = 'MLFlow'
├── Linked features
│   └── batch_size                  int                        32                                       
│       bottleneck_size             int                        16                                       
│       hidden_size                 int                        32                                       
└── Labels
    └── .ulabels                    ULabel                     glamorous-sow-773                        
/tmp/ipykernel_3331/2236147127.py:26: FutureWarning: Use features instead of params, params will be removed in the future.
  artifact.params
Artifact .ckpt
└── Linked features
    └── batch_size                  int                        32                                       
        bottleneck_size             int                        16                                       
        hidden_size                 int                        32                                       

See the checkpoints:

MLFlow checkpoints UI

If later on, you want to re-use the checkpoint, you can download it like so:

ln.Artifact.get(key="testmodels/mlflow/litautoencoder.ckpt").cache()
Hide code cell output
PosixUPath('/home/runner/work/lamin-mlops/lamin-mlops/docs/lamin-mlops/.lamindb/4DSByE7nnqos4Mkf0000.ckpt')

Or on the CLI:

lamin get artifact --key 'testmodels/litautoencoder'
ln.finish()
Hide code cell output
! cells [(9, 11)] were not run consecutively
→ finished Run('GJz3fAng') after 12s at 2025-06-03 11:34:26 UTC
Hide code cell content
!rm -rf ./lamin-mlops
!lamin delete --force lamin-mlops
! calling anonymously, will miss private instances
• deleting instance anonymous/lamin-mlops