Module 7 — Model registry and versioning
Module 5 produced a trained forecast model, Module 6 produced an AutoML candidate. Both are folders sitting inside their run's artifacts — findable if you remember the run ID, invisible otherwise. Registering a model in Azure ML gives it a name, a version, a set of tags, and a permanent lineage back to the data and the code that produced it. It is the object every endpoint and every pipeline points at.
The three model formats
Azure ML recognises three model types, and the choice affects deployment.
MLflow (mlflow_model) — a folder with an MLmodel file, a conda.yaml (or python_env.yaml) and the serialised weights. Azure ML knows how to load it, build a scoring server around it, and deploy it with no scoring script. This is the default and the target format.
Triton (triton_model) — the same folder structure but arranged for the NVIDIA Triton Inference Server, used when you need high-throughput GPU serving with model batching. Out of scope for the forecast project.
Custom (custom_model) — an arbitrary folder containing whatever your code needs (a pkl, an ONNX file, weights, tokeniser, config). Deployment then requires you to write a scoring script (score.py) that loads the file and defines the init() and run() functions Azure ML calls.
The rule of thumb: register as MLflow when the training code produced an MLflow model (which Module 5's mlflow.sklearn.save_model did), because Module 8's endpoints become one-line YAML with no custom scoring code.
Registering the baseline model
From a completed run, register the model output as version 1:
az ml model create \
--name forecast-lgbm \
--version 1 \
--path azureml://jobs/<job-name>/outputs/model \
--type mlflow_model \
--description "LightGBM baseline, 8-week horizon, valid MAPE 0.12" \
--tags framework=lightgbm horizon=8 champion=false owner=data-team
Two elements of that command carry the whole workflow to come.
The --path pointing at the job's outputs/model creates automatic lineage. In Studio, this model version links back to the exact job that produced it, which links back to the exact code, environment, and data-asset versions it consumed. If a support ticket in three months asks "what data was this trained on?", one click answers.
The tags are searchable. framework=lightgbm lets the endpoint deployment (Module 8) pick the right sibling model when the AutoML version arrives. champion=false will flip to true on the version that wins the shadow comparison. owner=data-team lets Module 10's cost dashboard attribute inference cost.
Versioning discipline
Model versions in Azure ML are immutable and monotonic: version 2 is a distinct artifact from version 1, and neither can be modified after registration. Two conventions turn that mechanism into a workflow.
One version per training run, one training run per version. Retraining next Sunday's run produces version 2. A hotfix to a scoring detail is not a new model version; it is a new endpoint deployment referencing the same version.
Tags express state, not identity. The tag champion=true moves from version to version as the challenger wins the traffic split. The version numbers themselves are stable and never reused.
Registering the AutoML candidate side by side
The AutoML run from Module 6 is registered under a distinct name — same problem, different lineage:
az ml model create \
--name forecast-automl \
--version 1 \
--path azureml://jobs/<automl-job-name>/outputs/best_model \
--type mlflow_model \
--tags framework=automl-ensemble horizon=8 champion=false
Both models now sit in the workspace registry, both traceable to their data, both deployable. Module 8's traffic split will decide which one gets to be the champion in production.
Workspace registry versus shared registry
The registry inside a workspace is scoped to that workspace. That is fine when one team owns dev, staging and prod versions of the same model. It stops being fine the moment the demand-forecast model needs to be consumed by a second workspace — say, a sibling project doing pricing analytics that wants to use the forecast as an input feature.
Azure ML offers a shared registry (an "Azure ML registry" resource, separate from any workspace) for exactly this. Publishing to the shared registry uses the same az ml model create command, targeting the registry instead of the workspace:
az ml model create \
--registry-name inskillops-shared \
--name forecast-lgbm \
--version 1 \
--path azureml://locations/westeurope/workspaces/mlw-forecast/models/forecast-lgbm/versions/1
Any workspace granted a Reader role on the shared registry can now reference azureml://registries/inskillops-shared/models/forecast-lgbm/versions/1 in its jobs and endpoints. Environments and components can be shared the same way, which turns the registry into the team's cross-project catalogue.
Reading lineage in Studio
In Studio, opening forecast-lgbm:1 shows the Lineage tab. It draws the graph: this model version → the job that produced it → the environment version, code snapshot and data-asset versions it consumed → the datastore each data asset points at. That is the answer to every "why does prod predict differently from what my notebook produced" investigation, and it is generated automatically as long as you registered from the job's output path.
Registering a model from a local path (--path ./my_model/) also works, and it loses everything. There is no job to link to, no environment recorded, no data-asset trace. That model exists but it is orphaned. Register from a run's outputs, always.
Summary
- Register models by type —
mlflow_modelfor one-line deployment,custom_modelwhen you write your own scoring script. - Versions are immutable; new state (champion, retired, staged) belongs on tags, not on new versions.
- Registering from a run's output path creates automatic lineage back to code, environment and data.
- Shared registries let multiple workspaces consume the same model, environment and components.
Next module: endpoints — deploying the registered model as a low-latency online service and as a weekly batch scoring job.