SageMaker pipelines
A SageMaker Pipeline turns a one-off training script into the automated, repeatable MLOps lifecycle: preprocess → train → evaluate → register, with approval-gated promotion.
- AWS credentials with SageMaker access +
pip install sagemaker - AWS credentials (
aws configure) +pip install boto3
Learning objectives
- Assemble a SageMaker Pipeline of processing → training → register steps.
- Register a trained model into the Model Registry with approval status.
- Explain how this automates the MLOps lifecycle from your LLMOps chapters.
- Trigger a pipeline run and inspect its execution.
code/aws9-sagemaker-pipelines/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.Pipelines automate the lifecycle intermediate
Here's the moment every ML team eventually lives through. A data scientist has a notebook that trains a great model. It works — on their laptop, with their copy of the data, run by their hands in the right order. Then someone asks the obvious question: "Can we retrain it next month on fresh data?" And suddenly nobody's sure. Which cell ran first? Which version of the preprocessing was used? Was that the model we evaluated, or the one before the last tweak? The notebook ran once and produced one model, and now that model is a snowflake — impossible to reproduce, impossible to trust, impossible to hand to anyone else.
Think of the difference between cooking a meal and writing down a recipe. A notebook is the meal: delicious, but gone, and you can only vaguely remember how you made it. A pipeline is the recipe — the exact steps, in order, that anyone (or any schedule) can follow to get the same dish every single time. A SageMaker Pipeline is that recipe expressed in AWS: a fixed, ordered set of steps — preprocess → train → evaluate → register — that AWS runs for you, the same way, on demand.
Now the precise definition, which will land because you feel the pain it solves: a SageMaker Pipeline is a directed acyclic graph (DAG) of steps, where each step's outputs feed the next, and the whole thing is defined as code, versioned, and re-runnable. "Directed acyclic" just means the work flows one way with no loops — data prep can't depend on training that hasn't happened yet. AWS tracks every execution, so you always know exactly which data produced which model, gated by which evaluation. That traceability — not the automation alone — is the real prize. This is the MLOps discipline, expressed in AWS primitives.
The common mistake to name up front: people assume a pipeline is "just my script wrapped in a scheduler." It isn't. The value isn't that it runs unattended — it's that each step is declared, its inputs and outputs are tracked, and the model that comes out the end carries a full lineage back to the data that made it. A cron job running a notebook gives you automation with none of that provenance. A pipeline gives you both, and provenance is what auditors, teammates, and your future self actually need.
A one-off training script is a demo. A SageMaker Pipeline is the automated, repeatable lifecycle: preprocess, train, evaluate, and register — versioned and re-runnable. This is the MLOps discipline, in AWS primitives.
Scan the top row left to right: that is the fixed order the pipeline runs, with each blue arrow meaning "this step's output feeds the next".
- The four boxes are the lifecycle stages — preprocess (blue, the start), train and evaluate (the work), and register (green, the payoff where the model is versioned and gated).
- The solid arrows only ever point right — that is what "directed acyclic" means: data prep can't depend on training that hasn't happened yet, so nothing loops back.
- The amber banner below, reached by the dashed arrow, is the real prize: the whole graph is declared as code, versioned, and re-runnable — so any run reproduces exactly, unlike a notebook you ran once.
In short: a notebook produces one un-reproducible model; this DAG produces the same model every time, with a tracked lineage from data to artifact.
Define the pipeline intermediate
Here's where the mental model usually breaks for newcomers. You sit down to write a pipeline expecting to write the work — the pandas code, the model.fit() call, the loop over epochs. Instead you find yourself writing something that looks oddly empty: a list of step objects with names and instance types, and no actual training logic in sight. It feels like you've written the table of contents but forgotten the book. That confusion is worth resolving, because it reveals what a pipeline definition really is.
A pipeline definition is a plan, not the labour. Picture a general contractor drawing up a build schedule: "first the foundation crew (2 days), then the framers (5 days), then electrical." The contractor isn't pouring concrete while writing the schedule — they're declaring who does what, in what order, on what equipment. When the SageMaker SDK asks you for a ProcessingStep with an SKLearnProcessor on an ml.m5.large, you're doing the same thing: naming a job, saying who runs it and on which cloud machine — not running it.
The two ingredients each step needs are worth separating clearly. The step (a ProcessingStep, a TrainingStep) is the what — a named box in the graph. The processor or estimator handed to it is the who and where — which container image does the work and what size instance it runs on. Keeping those two ideas distinct is the whole reason the API has two kinds of object: the step is the node in the DAG; the processor/estimator is the worker assigned to that node.
And the ordering rule is simpler than people fear: the order you list steps in is the order they run. When you write steps=[Preprocess, Train], you've declared that training depends on preprocessing finishing first. (In richer pipelines the dependency comes from one step consuming another's output — passing a training step the artifact a processing step produced — but the intuition is the same: outputs flow forward.) Finally, note the verb at the end: upsert — a blend of "update" and "insert." It ships the definition to AWS and saves the recipe; it does not cook. Nothing trains until a separate call starts an execution. The frequent beginner mistake is expecting upsert to kick off a run and then wondering why no model appeared — it only registered the plan.
ProcessingStep/TrainingStep is the named box in the graph; the SKLearnProcessor/Estimator handed to it says who does the work and on which instance — and upsert only saves this plan, it never trains.
Read each row as a pair: the left box names a step in the DAG, and the arrow points to the worker on the right that actually performs it.
- The left column is the what — the named nodes
ProcessingStep "Preprocess"andTrainingStep "Train"that you list in the pipeline. - The right column is the who and where — the
SKLearnProcessorandEstimator, each tagged with an instance type likeml.m5.largethat picks the cloud machine. - The amber banner at the bottom is the gotcha:
pipeline.upsert()ships this whole definition to AWS and saves it, but nothing trains — a separate call starts an execution.
In short: every step is a node plus a worker, and upsert registers the recipe rather than cooking it.
pipeline.pyfrom sagemaker.workflow.pipeline import Pipeline
from sagemaker.workflow.steps import ProcessingStep, TrainingStep
from sagemaker.sklearn.processing import SKLearnProcessor
from sagemaker.estimator import Estimator
ROLE = "arn:aws:iam::123456789012:role/sm-pipeline-role"
# Build a processor and an estimator (real args elided), then wire them
# into a repeatable DAG. This is the whole point of a Pipeline.
processor = SKLearnProcessor(framework_version="1.2-1", role=ROLE, instance_count=1,
instance_type="ml.m5.large")
estimator = Estimator(image_uri="<training-image>", role=ROLE, instance_count=1,
instance_type="ml.m5.large")
pipeline = Pipeline(
name="churn-model-pipeline",
steps=[
ProcessingStep(name="Preprocess", processor=processor),
TrainingStep(name="Train", estimator=estimator),
],
)
pipeline.upsert(role_arn=ROLE)
Instead of running a training script by hand every time, this builds a pipeline: a fixed sequence of steps AWS SageMaker can run for you, over and over, the same way. Think of it as an assembly line for building a machine-learning model — first clean the data, then train on it. This file just describes that assembly line; it doesn't run it yet.
- The
from sagemaker...lines pull in the building blocks: aPipeline(the assembly line), the step types (ProcessingStep,TrainingStep), and the workers that do the actual jobs (aSKLearnProcessorfor data prep, anEstimatorfor training). ROLEis an AWS IAM role ARN — a permission badge that says "this pipeline is allowed to use AWS resources on your behalf". The value here is a placeholder; you'd swap in your own.processorandestimatordefine who runs each job and on what hardware.instance_type="ml.m5.large"picks the size of the cloud machine;instance_count=1means one machine.Pipeline(name=..., steps=[...])is the key line: it lists the steps in order — firstPreprocess, thenTrain. That ordered list is the assembly line (engineers call it a DAG, a dependency graph).pipeline.upsert(role_arn=ROLE)sends this definition up to AWS and saves it. "Upsert" = update if it already exists, otherwise create it. After this, the pipeline exists in the cloud, ready to be triggered.
What the output means: Nothing visible prints. The result lives in AWS: a saved pipeline named churn-model-pipeline that you (or an automated schedule) can run on demand.
Try this: Add a third step to the steps=[...] list — for example an Evaluate step between train and register. The pipeline runs steps in the order you list them, so where you put it matters.
Model Registry advanced
Ask any team that has shipped ML to production about their worst afternoon, and a version of this story comes up. Model quality drops in production. Someone needs to roll back to "the good one." But which artifact was the good one? There are seven .tar.gz files in an S3 bucket named things like model-final, model-final-v2, and model-really-final. Nobody recorded which one scored what, or which one is actually live. The rollback becomes archaeology. This is the exact chaos the Model Registry exists to prevent.
Think of it as the version control for models — a Git-like ledger, but for trained artifacts instead of source. Every time a model is registered it gets a numbered version in a named group (e.g. version 3 in the churn group), and alongside the artifact the registry stores its metrics and, crucially, a status field called ModelApprovalStatus. That one field is the star of this section, so hold onto it.
Here's the precise idea and why it matters: ModelApprovalStatus is a gate, and the deploy step is built to read it. A version can sit at PendingManualApproval, and be flipped to Approved or Rejected. Your deployment is configured to ship only Approved versions — so promotion is never automatic on faith. A human, or an automated check, must set the status before the model can reach production. This decouples "a model exists and passed evaluation" from "a model is cleared to serve traffic" — two genuinely different decisions that beginners often collapse into one.
The subtle point people miss: the registry doesn't make the decision — it records and enforces it. Training and evaluation can happen fully automatically inside the pipeline; the approval flip is a deliberate, separate, auditable act. That separation is the backbone of an eval-gated rollout: the pipeline can register a candidate the moment it beats a baseline, but the candidate waits at the gate until someone opens it. It's the same discipline you met in O3 — and it's why "who approved this model, and when?" always has a clean answer.
Training outputs a model artifact; the Model Registry versions it, tracks metrics, and gates promotion with an approval status. Your deploy step only ships Approved versions — an eval-gated rollout, exactly like O3.
PendingManualApproval until someone flips ModelApprovalStatus to Approved or Rejected — and the deploy step ships only Approved, so promotion is never automatic on faith.
Start at the top box and follow the two branches down — that is the life of one model version as its approval status changes.
- The top box is a freshly registered version: it lands at
PendingManualApproval, existing and evaluated but not yet cleared. - The two arrows out are the deliberate flip a human or automated check makes — left to the green
Approvedbox, right to the redRejectedbox. - The bottom row is the payoff: the deploy step follows the solid arrow from Approved into production, while the dashed arrow from Rejected goes nowhere — that is the gate keeping bad versions out.
In short: the registry doesn't decide — it records and enforces one status field, and only Approved versions can be deployed.
approve.pyimport boto3
sm = boto3.client("sagemaker", region_name="us-east-1")
# promote a specific model version to Approved so deployment can pick it up
sm.update_model_package(
ModelPackageArn="arn:aws:sagemaker:us-east-1:123456789012:model-package/churn/3",
ModelApprovalStatus="Approved",
)
Training can produce many versions of a model over time. The Model Registry is AWS's catalogue of those versions. Before a model is allowed into production, a human (or an automated check) marks it Approved — a safety gate. This tiny script flips that switch on one specific version.
import boto3loads the AWS SDK for Python — the library that lets your code talk to AWS services.sm = boto3.client("sagemaker", region_name="us-east-1")opens a connection to SageMaker in a specific AWS region.smis now your handle for giving it commands.sm.update_model_package(...)is the command that changes a model version's status.ModelPackageArn="...model-package/churn/3"names exactly which version — here, version3of thechurnmodel group. The ARN is AWS's unique address for that item.ModelApprovalStatus="Approved"is the actual change: it promotes that version. Now a deploy step configured to ship onlyApprovedmodels will pick this one up.
What the output means: No printed output. Behind the scenes, version 3 of the churn model flips from pending to Approved in the registry, unblocking its deployment.
Try this: Change "Approved" to "Rejected". That's how you'd block a bad model version — the same gate, used to keep something out of production.
Run and inspect expert
You've written the recipe and saved it. Now comes a distinction that trips people up in every distributed system: defining the work and doing the work are two different acts. Your pipeline exists in AWS, versioned and ready — but it is idle. It has never trained anything. Nothing happens until you explicitly press Start. And once you do, a second surprise waits: the call that starts the run returns almost instantly, long before any training finishes.
That's because a pipeline run is asynchronous. Think of ordering food at a busy kitchen: you place the order and immediately get a ticket number — you don't stand frozen at the counter until the meal is cooked. start_pipeline_execution is placing the order; the ticket it hands back is the execution ARN, a unique ID for this specific run. AWS then works through your steps in the background while your script has already moved on.
This is why "run" and "inspect" are naturally two separate operations, and why one call cannot do both. To learn how the run is going, you take that ticket number and ask a different question: list_pipeline_execution_steps, passing the execution ARN. It reports each step and its current status — Executing, Succeeded, Failed. Because the run is live, that status is a snapshot in time: ask again a minute later and a step that was Executing may now read Succeeded. The mental model is a pizza-tracker — the order is placed once, but you refresh the status page repeatedly to watch it move.
The common mistake here is treating a single status check as the answer. One glance tells you where things stand right now, not whether the run will ultimately succeed. Real monitoring means polling — re-querying on an interval until every step reaches a terminal state (that's exactly what the practice ladder builds toward). And one gotcha to file away: for a brief moment right after you start a run, the step list can come back empty before AWS has registered the steps — so robust polling code checks for a non-empty list before deciding the run is done, or it will exit early on the first tick.
start_pipeline_execution hands back an execution ARN almost instantly while AWS runs the steps in the background; you then re-query list_pipeline_execution_steps with that ARN until every step reaches a terminal state.
Follow the top row left to right, then drop down and notice the loop — that curved arrow is the repeated part.
- The top-left box is the trigger call; the solid arrow shows it returns the amber execution ARN — the "ticket" for this one run — almost immediately.
- The dashed arrow to the right means AWS is off running the steps in the background; your script has already moved on, which is why the call felt instant.
- The bottom box is the status query: you feed it the ARN, and the curved dashed arrow looping back is polling every ~30 seconds. Each check is a snapshot — a step reading
Executingnow may readSucceedednext tick — until the green bar's condition (all steps terminal) is met.
In short: starting the run and reading its progress are two different calls, and monitoring means polling the ARN until every step is terminal.
run_pipeline.pyimport boto3
sm = boto3.client("sagemaker", region_name="us-east-1")
ex = sm.start_pipeline_execution(PipelineName="churn-model-pipeline")
print("execution:", ex["PipelineExecutionArn"])
steps = sm.list_pipeline_execution_steps(PipelineExecutionArn=ex["PipelineExecutionArn"])
for s in steps["PipelineExecutionSteps"]:
print(s["StepName"], s["StepStatus"])
The pipeline from Lab W9.1 is saved in AWS but idle. This script triggers a run of it and then checks on each step's progress — like pressing Start on the assembly line and watching a status board.
sm.start_pipeline_execution(PipelineName="churn-model-pipeline")kicks off one run of the pipeline by name. AWS begins working through the steps in the background.- The call returns an
exobject.ex["PipelineExecutionArn"]is the unique ID of this particular run — you'll need it to ask about progress. Theprintshows it to you. sm.list_pipeline_execution_steps(PipelineExecutionArn=...)asks AWS: "for this run, what are the steps and how are they doing?" It hands back a list.- The
for s in steps["PipelineExecutionSteps"]:loop walks that list and prints each step's name and status — e.g. whether it'sExecuting,Succeeded, orFailed.
What the output means: You'll see the run's ARN, then one line per step, such as Preprocess Succeeded and Train Executing. Run it again a minute later and the statuses will have moved forward.
Try this: Wrap the last three lines in a loop that re-checks every 30 seconds until every step shows Succeeded — that's the beginning of monitoring a pipeline automatically.
Exercise W9.1 — Eval-gated pipeline
Context: The capstone ties evaluation to promotion: a model earns registration only by beating a baseline, and earns deployment only by explicit approval. This register-if-good, deploy-if-approved contract is the backbone of safe automated rollout.
Your task: Add an evaluation step that computes a metric and only registers the model if it beats a baseline, register it as PendingManualApproval, then approve it and confirm a deploy step would pick it up.
Requirements:
- Compute an evaluation metric in a dedicated step (the Ch 8d pattern)
- Register the model only when the metric beats the baseline
- Register as
PendingManualApproval, not auto-approved - Approve the version and confirm a deploy step would select it
- Needs AWS creds; keep register-if-good and deploy-if-approved as two gates
💡 Hint: Reuse the industry rung's condition-gated RegisterModel, then perform approval as a separate update_model_package call to keep the human in the loop.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: A one-off training script is a demo; a SageMaker Pipeline is the automated, versioned, re-runnable lifecycle behind real MLOps. Understanding what the pipeline buys you — and the four stages it wires together — is the foundation for everything else in the lesson.
Your task: In your own words, explain what a SageMaker Pipeline gives you over running a training script by hand, and name the four lifecycle stages the lesson lists.
Requirements:
- Contrast a manual script with a repeatable, versioned, schedulable DAG of steps
- Name the four stages in order: preprocess → train → evaluate → register
- Explain that the same steps re-run identically every time
- Mention approval-gated promotion at the end of the flow
- Runs offline (a conceptual exercise)
💡 Hint: Frame it as MLOps discipline expressed in AWS primitives — the value is repeatability and version tracking, not any single step.
Show solution
A one-off training script is a demo; a SageMaker Pipeline is the automated, repeatable, versioned lifecycle you (or a schedule) can re-run the same way every time. It turns training into a DAG of steps rather than manual button-pushing.
The four lifecycle stages from the lesson:
preprocess -> train -> evaluate -> register
This is the MLOps discipline expressed in AWS primitives: the same steps, wired together, re-runnable and version-tracked, with approval-gated promotion at the end.
Context: Defining a pipeline is just describing an ordered list of steps and who runs each; the ordered list is the DAG. upsert then ships that definition to AWS (create or update) without running anything yet.
Your task: Using the SageMaker Workflow SDK, build a pipeline named churn-model-pipeline with a preprocessing step then a training step, and register it with upsert.
Requirements:
- Create a processor (e.g.
SKLearnProcessor) and anEstimator, each with a role and instance type - Wrap them in a
ProcessingStepand aTrainingStep - List the steps in order in
Pipeline(steps=[...])— that is the DAG - Call
pipeline.upsert(role_arn=ROLE)to register (create or update) - Nothing runs yet; needs AWS creds
💡 Hint: upsert only registers the definition — think of it as saving the recipe, not cooking; a separate call triggers a run.
Show solution
from sagemaker.workflow.pipeline import Pipeline
from sagemaker.workflow.steps import ProcessingStep, TrainingStep
from sagemaker.sklearn.processing import SKLearnProcessor
from sagemaker.estimator import Estimator
ROLE = "arn:aws:iam::123456789012:role/sm-pipeline-role"
processor = SKLearnProcessor(framework_version="1.2-1", role=ROLE, instance_count=1,
instance_type="ml.m5.large")
estimator = Estimator(image_uri="<training-image>", role=ROLE, instance_count=1,
instance_type="ml.m5.large")
pipeline = Pipeline(
name="churn-model-pipeline",
steps=[
ProcessingStep(name="Preprocess", processor=processor),
TrainingStep(name="Train", estimator=estimator),
],
)
pipeline.upsert(role_arn=ROLE)
The processor and estimator define who runs each job and on what hardware; Pipeline(steps=[...]) lists them in order — that ordered list is the DAG. ROLE is an IAM role ARN (a placeholder here) granting the pipeline permission to use AWS resources. pipeline.upsert(...) sends the definition up to AWS: update if it exists, otherwise create. Nothing prints and nothing runs yet — the pipeline just now exists in the cloud, ready to trigger. Needs AWS credentials to run (SageMaker access + pip install sagemaker).
Context: The Model Registry versions every trained model and tracks a ModelApprovalStatus per version. That single field is the gate a deploy step reads: flipping it to Approved ships a version; the same call with one word changed keeps a bad version out.
Your task: Write a boto3 script that promotes version 3 of the churn model group to Approved via update_model_package, and explain the one-word change that would block it instead.
Requirements:
- Call
update_model_packagewith the version-3ModelPackageArn(ending in/churn/3) - Set
ModelApprovalStatus="Approved"to unblock deployment - Explain that a deploy step ships only approved versions
- Note that
"Rejected"is the one-word change that blocks it - Needs AWS creds
💡 Hint: The ARN's trailing /churn/3 pins exactly one version — the approval status is the only thing you are changing.
Show solution
import boto3
sm = boto3.client("sagemaker", region_name="us-east-1")
# promote a specific model version to Approved so deployment can pick it up
sm.update_model_package(
ModelPackageArn="arn:aws:sagemaker:us-east-1:123456789012:model-package/churn/3",
ModelApprovalStatus="Approved",
)
The Model Registry versions each trained model and tracks a ModelApprovalStatus. update_model_package flips the status of one specific version — the ModelPackageArn ending in /churn/3 names exactly version 3 of the churn group. Setting it to "Approved" unblocks a deploy step configured to ship only approved versions. Changing the single value to "Rejected" uses the same gate to keep a bad version out of production. Needs AWS credentials to run (aws configure + pip install boto3).
Context: Starting a run and reading its progress are two different calls: one kicks off an execution by name, the other queries per-step status using the execution ARN as the handle. This is the basis of every monitoring script you will build on top.
Your task: Start a run of churn-model-pipeline, print its execution ARN, then list each step with its status.
Requirements:
- Call
start_pipeline_execution(PipelineName=...) - Print the returned
PipelineExecutionArn - Pass that ARN to
list_pipeline_execution_steps - Loop and print each step's
StepNameandStepStatus - Needs AWS creds and an already-upserted pipeline
💡 Hint: The execution ARN is the handle for this run — everything you query about progress keys off it, and statuses advance if you re-run a minute later.
Show solution
import boto3
sm = boto3.client("sagemaker", region_name="us-east-1")
ex = sm.start_pipeline_execution(PipelineName="churn-model-pipeline")
print("execution:", ex["PipelineExecutionArn"])
steps = sm.list_pipeline_execution_steps(PipelineExecutionArn=ex["PipelineExecutionArn"])
for s in steps["PipelineExecutionSteps"]:
print(s["StepName"], s["StepStatus"])
start_pipeline_execution kicks off one run by name; AWS works through the steps in the background. The returned ex["PipelineExecutionArn"] uniquely identifies this run and is the handle you pass to list_pipeline_execution_steps to fetch progress. The loop prints each step's name and status — e.g. Preprocess Succeeded, Train Executing. Re-run a minute later and the statuses will have advanced. Needs AWS credentials to run (boto3 + a pipeline already upserted).
Context: "Kick it off and hope" is not monitoring. Turning the trigger script into a poller that re-checks step status until every step reaches a terminal state is the seed of the automated pipeline monitoring you would wire into alerting.
Your task: Extend the run script into a monitor: after triggering the pipeline, re-check step status every 30 seconds and stop when all steps have finished (Succeeded or Failed).
Requirements:
- Start the execution and capture its ARN
- Define a set of terminal states (e.g. Succeeded, Failed, Stopped)
- Loop, re-querying
list_pipeline_execution_stepsand sleeping ~30s between checks - Guard against the brief empty-steps window right after start
- Exit only when all steps are terminal, then report overall success/failure; needs AWS creds
💡 Hint: Steps can be momentarily empty right after start, so require a non-empty list and all-terminal before breaking, or you will exit early.
Show solution
import boto3, time
sm = boto3.client("sagemaker", region_name="us-east-1")
ex = sm.start_pipeline_execution(PipelineName="churn-model-pipeline")
arn = ex["PipelineExecutionArn"]
print("execution:", arn)
TERMINAL = {"Succeeded", "Failed", "Stopped"}
while True:
steps = sm.list_pipeline_execution_steps(PipelineExecutionArn=arn)["PipelineExecutionSteps"]
statuses = {s["StepName"]: s["StepStatus"] for s in steps}
print(statuses)
if steps and all(v in TERMINAL for v in statuses.values()):
break
time.sleep(30)
print("done:", "FAILED" if "Failed" in statuses.values() else "ALL SUCCEEDED")
This is the lesson's "Try this" turned into real monitoring: the loop re-queries list_pipeline_execution_steps every 30 seconds and exits once every step reaches a terminal state, then reports overall success or failure. Note steps can be empty for a moment right after start, so the steps and ... guard prevents a premature exit. This is the seed of automated pipeline monitoring you'd wire into alerting. Needs AWS credentials to run (boto3 + an upserted pipeline).
Context: Production promotion is never automatic on faith. The eval-gated pattern registers a model only if it beats a baseline metric, and even then lands it as PendingManualApproval so a human clears it before any deploy step picks it up.
Your task: Implement W9.1: add an evaluation step that registers the model only if it beats a baseline, register it as PendingManualApproval, then approve it so a deploy step would pick it up.
Requirements:
- An
evaluatestep emits a metric (e.g. accuracy) - A
ConditionStepwithConditionGreaterThanOrEqualTogates registration on beating the baseline (e.g. 0.80) RegisterModelusesapproval_status="PendingManualApproval"(not auto-shipped)- Wire preprocess → train → evaluate → gate into the pipeline and
upsert - Then flip the version to
Approvedwithupdate_model_package; needs AWS creds
💡 Hint: The condition's if_steps is where the register lands — below-baseline runs simply skip it, and approval stays a deliberate second, human step.
Show solution
from sagemaker.workflow.pipeline import Pipeline
from sagemaker.workflow.steps import ProcessingStep, TrainingStep
from sagemaker.workflow.condition_step import ConditionStep
from sagemaker.workflow.conditions import ConditionGreaterThanOrEqualTo
from sagemaker.workflow.step_collections import RegisterModel
ROLE = "arn:aws:iam::123456789012:role/sm-pipeline-role"
# ... processor, estimator, and an 'evaluate' ProcessingStep that emits accuracy ...
register = RegisterModel(
name="RegisterChurn",
estimator=estimator,
model_package_group_name="churn",
approval_status="PendingManualApproval", # not auto-shipped
)
gate = ConditionStep(
name="GateOnAccuracy",
conditions=[ConditionGreaterThanOrEqualTo(left=eval_accuracy, right=0.80)],
if_steps=[register], # only register if it beats the 0.80 baseline
else_steps=[],
)
pipeline = Pipeline(name="churn-model-pipeline",
steps=[preprocess, train, evaluate, gate])
pipeline.upsert(role_arn=ROLE)
The condition step is the eval gate: the model is only registered when its accuracy clears the baseline, and even then it lands as PendingManualApproval rather than auto-approved. A human (or automated check) then promotes it, exactly as in Lab W9.2:
import boto3
sm = boto3.client("sagemaker", region_name="us-east-1")
sm.update_model_package(
ModelPackageArn="arn:aws:sagemaker:us-east-1:123456789012:model-package/churn/3",
ModelApprovalStatus="Approved",
)
Together this is the eval-gated rollout the lesson describes: register only if it beats a baseline, deploy only Approved versions. Needs AWS credentials to run (SageMaker SDK + boto3).
✓ Checkpoint — you can move on when you can…
- Assemble a multi-step SageMaker Pipeline.
- Register a model version and set its approval status.
- Explain how the Model Registry enables eval-gated rollouts.
- Trigger a run and inspect per-step status.
Knowledge check check yourself
What does a SageMaker Pipeline give you over a one-off training script, and what does pipeline.upsert do?
Show answer
How does the Model Registry enable an eval-gated rollout?