Skip to main content

Module 6 — Model Registry and versions

Module 5 produced a winning model as a file in Cloud Storage. That is not enough to serve traffic responsibly. This module puts the file into the Model Registry — Vertex's catalogue of versioned model artifacts with their metadata, evaluation and lineage — so that module 7 can deploy it, and any teammate can answer "what is in production?" in one query.

Why a registry, and not just a file path

A trained model is only useful if you can answer three questions about it later: what data was it trained on, what code produced it, and how good was it on a held-out set. Storing the file at gs://.../models/final.bst and calling it done erases all three answers by the next training run.

The Model Registry solves this by wrapping the file into a Model resource with:

  • A version (auto-incrementing when uploaded to the same parent).
  • Aliases (movable labels like default, production, champion).
  • Attached metadata: training job that produced it, dataset URIs, evaluation metrics, dataset schema.
  • A serving container already declared — deployment in module 7 needs only an endpoint.

A model in the registry is also the object that appears in the lineage graph of module 9: pipelines that use it, endpoints that serve it, batch jobs that call it, all connect back through this one resource.

The upload, done properly

There are three routes to get a model into the registry:

  1. Return it from job.run() on a CustomTrainingJob (what module 4 already did — the model is uploaded automatically).
  2. Call aiplatform.Model.upload(...) directly, when the file was produced elsewhere.
  3. Ingest it from a Model Garden fine-tune (module 10).

The direct upload is worth seeing explicitly, because production uploads (from CI, from a KFP pipeline) look like this rather than like the notebook path:

from google.cloud import aiplatform

aiplatform.init(project="fraud-detection-dev", location="europe-west1")

model = aiplatform.Model.upload(
display_name="fraud-xgb",
parent_model="projects/…/models/1234567890", # None on the first upload
artifact_uri="gs://fraud-detection-dev-vertex-eu/artifacts/fraud-xgb/v7/",
serving_container_image_uri=(
"europe-docker.pkg.dev/vertex-ai/prediction/xgboost-cpu.1-7:latest"
),
serving_container_predict_route="/predict",
serving_container_health_route="/health",
is_default_version=True,
version_aliases=["challenger"],
version_description="Vizier trial 47, aucpr=0.86 on 2026-06 validation split.",
labels={"env": "dev", "team": "fraud", "framework": "xgboost"},
)

Two rules that pay off two months later.

Always set parent_model starting from the second upload. Without it, Vertex creates a new model rather than a new version of the existing one, and the console fills with fraud-xgb, fraud-xgb-v2, fraud-xgb-final, fraud-xgb-final-final. Every experiment that shares a prediction target belongs under the same parent_model.

Write a version_description that is useful in a hurry. "Trained by Alice" is not; "Vizier trial 47, aucpr=0.86 on 2026-06 validation split" tells the on-call engineer at 3 a.m. which version is which.

Versions and aliases: which one moves

Versions are immutable: version 7 is version 7 forever, with its artifact URI and its metadata frozen. Aliases are movable pointers: default might point at version 5 today and version 7 tomorrow.

The convention that scales to a team is:

AliasMeaningMoved by
defaultThe version served by an endpoint that does not name a versionModel.update_version(..., version_aliases=["default"])
championThe version currently serving production trafficDeployment pipeline (module 9)
challengerA candidate under evaluationA/B test workflow
previousThe prior champion, kept for rollbackRotated by the same pipeline

An endpoint deployment in module 7 that reads model_version="default" benefits from this indirection: promoting a new version is one alias move away, and no endpoint config needs to change.

# Promote v7 to champion, kick v5 back to previous
model.update_version(version="7", version_aliases=["champion"])
model.update_version(version="5", version_aliases=["previous"])
Aliases you cannot use as names

default, latest and any string starting with a digit are reserved or rejected. Team-defined aliases (champion, challenger, holdout-2026-q3) are fine. Attempting to set an invalid alias returns a 400 Bad Request from the API — worth catching in a CI script rather than discovering during a release.

Attaching evaluation to a version

An unlabelled model version is a "trust me" model. Attaching an evaluation makes the numbers auditable in the console and readable by downstream jobs.

from google.cloud.aiplatform import model_evaluation

evaluation = model.upload_evaluation(
display_name="2026-06 validation split",
metric_type="classification",
metrics={
"auRoc": 0.972,
"auPrc": 0.861, # the metric that actually matters here
"logLoss": 0.086,
},
prediction_type="classification",
ground_truth_column="is_fraud",
slice_dimensions=["merchant_category"], # per-category metrics if provided
)

For a class-imbalanced problem like fraud (0.2% positives), PR-AUC is the number to defend, not ROC-AUC — a point module 7 comes back to when choosing the decision threshold. The slice_dimensions field enables per-category evaluation, which surfaces the "great on average, awful on food-delivery merchants" pattern that averages hide.

Lineage: how the registry connects to everything else

Vertex's metadata store records lineage automatically whenever the SDK is involved. A model uploaded by a CustomTrainingJob gets edges to:

  • The training job that produced it (with its args, machine type, container tag).
  • The dataset URIs the job read (whether GCS or BigQuery).
  • Any pipeline run it participated in (module 9).
  • Any endpoint it is deployed to and any batch job that called it.

In the Model Registry UI, the Lineage tab draws this as a graph. In code, the same information is available via aiplatform.Metadata:

for e in aiplatform.Execution.list(
filter=f'metadata.output_model="{model.resource_name}"'):
print(e.display_name, e.metadata)

The value of lineage compounds. Answering "which training data produced version 7?" or "which endpoints are on a version older than three months?" becomes a query rather than an archaeological dig through Slack and old scripts.

The pattern of a healthy registry

Three habits keep a Model Registry useful over a year:

  • One model per prediction target. All fraud models live under fraud-xgb, tuning experiments and rewrites included. Not one per author, not one per week.
  • Aliases are named after roles, not versions. champion moves; v7 does not. Endpoints refer to roles.
  • Evaluation attached before promotion. A version that has no evaluation cannot become champion. This one rule prevents the majority of "we shipped the wrong model" incidents.

In summary

  • The Model Registry turns a file in Cloud Storage into a versioned, catalogued resource with evaluation, lineage and a declared serving container.
  • Use parent_model on every upload after the first to accumulate versions under one model; use aliases (champion, challenger, previous) to represent roles that move.
  • Attach evaluation with the metric that matches the problem — PR-AUC for the class-imbalanced fraud task — and slice it by natural dimensions like merchant category.
  • Lineage is automatic when the SDK is used end to end; that graph turns "who trained this model on what data" into a one-line query.

Next module: exposing versions of the model to traffic — endpoints, a 90/10 split between two versions, replica autoscaling and request logging.