Skip to content

Track a training run with MLflow

Use this guide to fit a tiny regression model and save its measured holdout metrics, parameters and chosen configuration to a local MLflow experiment. The result is an inspectable run ID; packaging and registration are separate next steps.

Install and choose a store

Use Python 3.12 or newer and a virtual environment:

uv pip install "skyulf-core[mlflow]"

For repeatable jobs, pin the published Skyulf release and its resolved dependencies in your environment lock, and use the same versions when loading models. Repository development examples require the matching checkout/build when they use APIs newer than your installed release. Do not infer release availability from the version printed by an editable development install.

Fit, evaluate and log real results

Run this entire block in an empty working directory. It creates mlflow.db and the artifact storage managed by MLflow; keep both to inspect the run later. No Databricks account or Spark session is needed.

from pathlib import Path

import pandas as pd
from mlflow import MlflowClient

from skyulf.data.dataset import SplitDataset
from skyulf.inference.fitted_pipeline import load_pipeline, save_pipeline
from skyulf.inference.pipeline_evaluation import evaluate_holdout
from skyulf.integrations.mlflow import TrackingConfig, track_run
from skyulf.pipeline import SkyulfPipeline

frame = pd.DataFrame({
    "x": [1., 2., 3., 4., 5., 6., 7., 8.],
    "target": [2., 4., 6., 8., 10., 12., 14., 16.],
})
training, heldout = frame.iloc[:6].copy(), frame.iloc[6:].copy()
pipeline_config = {
    "preprocessing": [],
    "modeling": {"type": "linear_regression"},
}
tracking_uri = "sqlite:///" + (Path.cwd() / "mlflow.db").as_posix()
config = TrackingConfig(
    enabled=True, tracking_uri=tracking_uri,
    experiment_name="revenue-demo", failure_policy="raise",
)
with track_run(config, run_name="linear-baseline") as run:
    pipeline = SkyulfPipeline(pipeline_config)
    pipeline.fit(SplitDataset(train=training, test=heldout), target_column="target")
    save_pipeline(pipeline, "artifacts/revenue")
    metrics = evaluate_holdout(
        load_pipeline("artifacts/revenue"), heldout, target_column="target"
    )
    run.log_params({"model": "linear_regression", "training_rows": len(training)})
    run.log_metrics(metrics)
    run.set_tags({"dataset_id": "synthetic-v1/holdout-rows-7-8"})
    run.log_config(pipeline_config)
    run_id = run.run_id

stored = MlflowClient(tracking_uri=tracking_uri).get_run(run_id)
assert stored.info.status == "FINISHED"
assert stored.data.metrics["heldout_rmse"] < 1e-10
assert "config_sha256" in stored.data.params
print(run_id, stored.data.metrics)

Expect a concrete run ID and heldout_rmse close to zero, alongside the other available regression metrics. These are measured values from the saved model, not example numbers sent directly to the tracker. save_pipeline writes a local artifact; this block uploads only the explicit tracking metadata and config. Use model packaging to upload a loadable model too.

Start the UI from the same directory, then open the printed URL:

mlflow ui --backend-store-uri sqlite:///mlflow.db --port 5000

Choose Experiments > revenue-demo > linear-baseline. Inspect Metrics for heldout_rmse, Parameters for training_rows and config_sha256, and Artifacts > config.json for the configuration. This manual example does not populate Inputs because it does not call log_inputs.

Turn tracking off or choose a failure policy

The default is TrackingConfig(enabled=False). The handle exposes the same methods but constructs no client and makes no tracking network calls:

from skyulf.integrations.mlflow import TrackingConfig, track_run

with track_run(TrackingConfig(), run_name="offline-fit") as offline:
    offline.log_metrics({"example_metric": 1.0})  # discarded
assert offline.run_id is None
Choice Default and effect
enabled False; no tracking client or run is created.
tracking_uri None; enabled tracking uses MLflow's configured store. Set it explicitly in jobs.
experiment_name None; uses the default experiment. A supplied name is looked up or created.
failure_policy "raise"; propagate tracking failures. "warn" preserves execution and records run.tracking_error for the caller to inspect.

The adapter binds every call to its own run ID, leaves a caller-owned active run open, and marks its run FINISHED on success or FAILED when the training body raises. It does not write backend job metadata. Explicit model-packaging failures still propagate; warn is not a model-publication retry policy.

log_config is explicit and records config_sha256. It does not automatically serialize the whole pipeline, input rows, secrets or environment variables. Review anything you pass to params, tags and config before logging it.

Symptom Action
No run appears Confirm enabled=True, the tracking URI and selected experiment.
Authentication/store error Configure access to the chosen store; use warn only if missing tracking is acceptable and record tracking_error.
Run is failed Inspect the training exception; tracking does not make a failed fit successful.
No model in Artifacts Call log_pipeline_model or log_model; fitting/tracking alone does not package it.
No datasets in Inputs Manual tracking requires explicit log_inputs; candidate training logs them automatically as described below.

Training datasets in Inputs

For the run's model/, lifecycle/ and training JSON files, see the experiment artifact reference.

Databricks candidate training records two native MLflow dataset inputs on the run that produces the model: training and holdout. This happens automatically for train_candidate and generated Bundle training jobs, with either pandas or Polars. Calling SkyulfPipeline.fit alone does not create a tracking run.

Open the registered model version, follow its source run, then open Inputs. For a model competition, each fitted candidate has its own inputs and the winning model's parent run records the selected candidate's inputs. For multiple targets, inspect the individual model's run; the orchestration parent does not fit a model.

Evidence Where to inspect it
Actual fitted and held-out row counts, schema and digest The training and holdout dataset entries. Counts describe the bounded splits after source filtering and sampling, rather than the entire Delta table.
Original Delta table and pinned version, including version 0 Each dataset's source metadata. Native Unity Catalog identity is included when captured inside Databricks.
Model feature names, target and optional weight column Input tags skyulf.feature_columns, skyulf.target_column and skyulf.weight_column. A target or weight column is not an input feature merely because it appears in a dataset schema.
Split, sampling, source identity and feature lookup evidence Artifacts > dataset_inputs.json. Each input's source metadata references this artifact and its SHA-256 digest.

The dataset represents the frame passed to fitting or final evaluation, before the pipeline's fitted preprocessing transforms it. Sample weights are supplied separately to fitting; their column role and existing weight evidence remain in the provenance. Cross-validation folds do not create a fictitious validation split in this view.

Inputs are metadata, not an additional upload of every data row or a backup of the source table. The native Delta source identifies the originating snapshot; loading that source alone does not reconstruct filtering, sampling or split membership. Keep the referenced Delta versions and the run's existing artifacts for reproducibility. Later table updates do not rewrite previously logged inputs.

This describes the Inputs entries, not every artifact a workflow may save. Candidate training with a feature-lookup binding separately saves enriched holdout rows in Artifacts > lifecycle/data/holdout.parquet for comparison-bound approval replay. Saved model state and explicitly enabled reports can also contain data-derived values. Review artifact access/retention alongside source permissions; an Inputs digest is not a promise that the run contains no data.

For custom tracked code, run.log_inputs(dataset_inputs) accepts native mlflow.entities.DatasetInput records and writes to that handle's explicit run ID. It follows the same disabled-tracking and failure-policy behavior as the other logging methods. See the native MLflow client API for the dataset input format.

Training preprocessing diagnostics

With TrainingSpec(..., preprocessing_probe=True) or defaults.save_preprocessing_diagnostics: true in a generated Bundle, the run that fitted each pipeline saves preprocessing_probe.json in its Artifacts tab. The fitting task's Output (train_and_tune or train_<name>) shows the result; the final training_report shows only a status column and link. The preprocessing diagnostics guide explains the check and every status.