MLOps & LLMOps / The Model Lifecycle
Promoting a model and reverting fast when it regresses.
Reviewed by Yuvaraj
In model operations, a deployment is not a code push, it is the promotion of a specific, immutable model version from a registry into the position that live traffic resolves to. Rollback is the inverse move: repointing that position back to a previous version you deliberately kept available, restoring known-good behavior in seconds rather than rebuilding it. Treating both as pointer changes over pinned, reproducible artifacts is what separates a controlled model lifecycle from a risky one, and it is why rollback must be designed and rehearsed before the deploy, not improvised during an incident.
When you deploy an application you ship code; when you deploy a model you ship a version, a bundle whose identity is fixed by an immutable version number or a content hash. A model registry (MLflow, SageMaker Model Registry, Vertex AI, Weights & Biases) records each version alongside its lineage: the training run, the dataset snapshot, the code commit, the hyperparameters, and the offline metrics.
Production does not point at "the model." It points at a version through a movable alias (for example champion@production). Today that alias resolves to v4; promoting v5 only repoints the alias. Crucially, the v4 artifact is never mutated or deleted. That immutability is the entire mechanism behind fast rollback, reverting is just resolving the alias back to a version that still exists, byte-for-byte, exactly as it served before.
Alias, not stage
Older registries used hard lifecycle stages (Staging, Production). Modern
practice, MLflow deprecated stages in favor of aliases and tags in its 2.x
line, treats promotion as moving a named pointer to an immutable version. The
concept is the same: one alias resolves to exactly one version at a time.
Ask about this lesson, or about anything in AI. Answers cite the lessons they draw on.
Finished this lesson?
Mark it complete to earn XP, keep your streak, and schedule a review.
Promotion is a registry state change guarded by pre-promotion checks. A new version does not earn the production alias by existing, it earns it by clearing every gate:
Progressive exposure, shadow, then a small canary percentage, then full, reduces blast radius, but the artifact discipline here is orthogonal to traffic splitting: what matters is which immutable version the alias resolves to and whether you can flip it back instantly.
A rollback restores prior behavior only if you pinned the entire stack together under one version identity. Reverting the weights while a preprocessing step, tokenizer, or threshold silently moved on gives you a Frankenstein that matches neither v4 nor v5. Everything below travels as one immutable bundle:
| What to pin | Why a clean rollback needs it |
|---|---|
| Model weights / checkpoint | The learned parameters, the obvious artifact. |
| Model + serving code | Architecture and inference logic; a code change alters outputs even with identical weights. |
| Preprocessing / feature pipeline | Feature transforms and normalization must match how the model was trained and last served. |
| Tokenizer / vocabulary | A shifted vocab or merge table quietly changes every input the model sees. |
| Config & thresholds | Decision cutoffs, max tokens, sampling settings, prompt templates. |
| Runtime & dependencies | Framework and library versions pinned (for example torch-2.3.1) to avoid numerical drift. |
| Baseline metrics | The previous version's live SLI values, so a guard has something concrete to compare against. |
A version is a promise, not a file
The identity of a deployment is the complete bundle, not the checkpoint alone. Rollback restores exact prior behavior only when that identity is complete, pin the parts together, hash them together, and promote them together.
Rollback should be as boring and fast as a config change: repoint the alias, and the router serves the warm previous version. Two properties make that possible. First, the old version stays warm, loaded in memory or held in a warm pool, because cold-loading a large model can take minutes, and minutes are exactly what you do not have while a metric is regressing. Second, the revert path is rehearsed: run game days where you trip a guard on purpose and confirm traffic lands back on the prior version. An untested rollback is not a rollback.
Rollback triggers watch live service-level indicators against the pinned baseline: quality signals (guardrail failure rate, downstream acceptance, thumbs-down rate), operational signals (error rate, p95 latency, cost per request), and safety signals. To avoid reacting to noise, evaluate over a sustained window with a minimum sample size. A compact trigger rule: revert when the windowed live error rate exceeds the baseline by a tolerance, , sustained across consecutive windows that each carry enough traffic to be trustworthy:
where is the measured error rate in window , the pinned previous-version baseline, the tolerance, the number of consecutive windows required, and the minimum requests per window.
The mature pattern uses both: automatic reverts for fast, clear operational SLIs, and a one-click manual revert for quality regressions that only surface once labels or human feedback arrive. Both resolve to the same pinned artifact.
A support-ticket ranking model serves production as support-ranker@production → v4. Its live one-hour baseline is an error rate of 1.8% (responses failing schema or guardrail validation), p95 latency 410 ms, at roughly 120 req/s. The team promotes v5 behind a release manifest that pins the whole bundle and wires an automatic guard.
# release manifest, one immutable bundle, one version id
model:
name: support-ranker
version: v5 # immutable; resolves to a content hash
artifact_uri: s3://models/support-ranker/v5/ # write-once
sha256: 9f2c...e01 # integrity check at load time
pinned_with_the_weights:
code_commit: a1b9c74 # model + serving code
runtime: python-3.11, torch-2.3.1
preprocessing: tokenizer-2025.06 # feature/preprocess pipeline version
config: { max_tokens: 512, threshold: 0.61 }
promotion:
from: staging
to: production
requires: [checksum_ok, golden_eval_pass, load_test_pass]
rollback:
keep_warm: v4 # previous prod stays loaded
auto:
metric: error_rate_10m
baseline: 0.018 # v4 live baseline
trigger_above: 0.033 # baseline + 1.5pp tolerance
sustain_windows: 2
min_requests: 2000
revert_to: v4
Offline, v5 looked strictly better: golden-set F1 up 0.6 points over v4, load test p95 430 ms at 150 req/s. All gates green, so at 09:00 the alias is repointed to v5 and v4 is kept warm. Then the live numbers diverge from the offline story:
| Time | Event | Live error rate | Note |
|---|---|---|---|
| 09:00 | Alias → v5, v4 kept warm | 1.9% | Within baseline noise |
| 09:20 | Error rate creeping | 2.9% | Below the 3.3% trigger |
| 09:40 | First 10-min window closes | 4.2% (n ≈ 71k) | Over threshold, window 1 of 2 |
| 09:50 | Second window closes | 4.6% (n ≈ 73k) | Sustain condition met |
| 09:51:12 | Guard trips → alias reverts to v4 | , | Automatic, no human |
| 09:51:40 | Traffic on warm v4 | 1.9% | Back to baseline; MTTR under a minute |
The guard fired because held across two consecutive windows that each cleared the 2000-request floor. Reverting was a pointer flip to an already-loaded model, so the error rate returned to baseline within seconds instead of the minutes a cold reload would have cost. Elevated errors lasted about 50 minutes on a bounded slice, the error budget was dented, not blown, and there was never a full outage.
Root cause: v5 shipped with an updated tokenizer (tokenizer-2025.06) that had not been the version the offline golden eval ran against, so the regression was invisible until real traffic hit the mismatched preprocessing. The fix was exactly the pinning discipline above: bind preprocessing into the version bundle, re-run the smoke eval through that exact bundle, and only then re-attempt promotion.
Common mistakes