This page is for anyone facing an MLOps or ML platform round, from a first job to a senior role. Most interviews start with what MLOps is and how models get packaged and served, then move to CI/CD and continuous training, model registries, reproducibility and feature stores. Stronger rounds test drift monitoring, retraining triggers, shadow and canary rollouts, GPU cost and the LLM slice: serving large models and running evals. Each question shows what the interviewer is really checking, the shape of a strong answer and a short answer to say out loud. Practise them, then swap in your own stories.
Search all questions by round, difficulty and level, or save the ones you want to practise.
Definition: the practices and tooling that take models from experiment to reliable production and keep them healthy there.
What's extra: data and models are versioned artifacts too, and a model can degrade while the code stays the same.
New loops: continuous training, model registry, drift monitoring and retraining on top of normal CI/CD.
"MLOps is the set of practices for getting models into production reliably and keeping them working once they're there. It borrows a lot from DevOps: version control, automated tests, CI/CD, monitoring. The difference is that in an ML system the behaviour depends on three things, not one: the code, the data it was trained on, and the trained model itself. So I have to version all three. The other big difference is that a model can get worse without anyone touching it, because the world changes and the incoming data drifts away from the training data. That's why MLOps adds pieces a normal service doesn't have: experiment tracking, a model registry, pipelines that retrain on their own, and monitoring of the data and predictions, not just CPU and error rates."
Describing MLOps as just putting a model in a container, with no mention of data versioning, retraining or monitoring model quality.
Approach: templates, shared tooling and early involvement instead of a handover at the end.
Disagreement: what it was about and each side's reason.
Resolution: how you settled it and what both sides learned.
"My approach is to make the production path the easy path. I give data scientists a project template that already logs to the tracking server, saves the full pipeline and has a serving wrapper, and I join early design chats instead of receiving a notebook at the end. One disagreement I remember: a data scientist wanted to use a feature built from a slow reporting query that ran once a day, and the model was meant to score in real time. She saw a clear lift offline. I worried the feature would be up to a day stale at serving, which her offline test didn't reflect. We settled it with data: I rebuilt the training set with the feature lagged the way it would really be served. Most of the lift survived, so we kept it, and we both started testing features under serving conditions by default."
Describing data scientists as the problem, or a process where engineers just rewrite their models without them.
Batch: score everything on a schedule, write results to a table; cheap and simple when inputs are known ahead.
Online: score on request with a latency budget; needed when the input only exists at request time.
Middle ground: streaming scoring on events, or precomputing most features and scoring online.
"I start from when the input exists and how fresh the answer has to be. If I can know the inputs ahead of time, like scoring every customer for churn once a night, batch is my default. A scheduled job scores everything, writes to a table, and the app just reads it. It's cheap, easy to debug and there's no latency pressure. I need a real-time endpoint when the input only exists at the moment of the request, like a fraud check on a card payment or ranking search results for the query someone just typed. That brings a latency budget, autoscaling and an on-call burden, so I only pay that cost when the product needs it. There's also a middle path: scoring events from a stream as they arrive, or precomputing heavy features in batch and doing a light online lookup."
Saying every model should sit behind a real-time API, or not mentioning latency and freshness as the deciding factors.
Preprocessing: the exact transforms used in training, saved as part of the pipeline, not rewritten.
Environment: pinned library versions, since a pickled model may not load under different versions.
Contract and metadata: input and output schema, model version, training data reference, metrics.
"The weights on their own aren't enough. First, the preprocessing: scaling, encoders, tokenisers, whatever turned raw input into features during training. I save those together with the model as one pipeline object, so serving can't quietly do it differently. Second, the environment. Formats like pickle or joblib can break or behave differently if the library version changes, so I pin the exact versions and usually build a container image from them. Third, a contract: the input schema with names and types, the output format, and metadata like the model version, which data snapshot and code commit produced it, and its evaluation metrics. Tools like MLflow bundle a lot of this, with a signature and the environment file, but the principle is the same whatever tool I use: someone should be able to load and call it without asking me anything."
Handing over only the model file and expecting the serving team to rebuild the preprocessing and figure out the library versions.
Load once: the model loads at startup, never inside the request handler.
Validate input: a typed schema rejects bad requests before they reach the model.
Traceable output: every response carries the model version.
Before production: health checks, logging of inputs and outputs, timeouts, load testing.
"I load the model at startup, because loading it per request would add seconds of latency. Input goes through a typed schema, so a missing field or a string where I expect a number gets a clear 422 error instead of a strange prediction. The handler builds a one-row frame with the same column names used in training and returns the score with the model version, which I read from the environment so every prediction can be traced back. Before real traffic I'd add a readiness check that runs one test prediction, structured logs of inputs and predictions for monitoring, a request timeout, and a load test to find how many workers one instance can handle. If throughput mattered a lot, I'd look at batching requests together."
import os
import joblib
import pandas as pd
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
model = joblib.load("model.joblib") # full pipeline, loaded once
MODEL_VERSION = os.getenv("MODEL_VERSION", "unknown")
class Features(BaseModel):
tenure_months: int
monthly_spend: float
support_tickets: int
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/predict")
def predict(f: Features):
row = pd.DataFrame([f.model_dump()])
score = float(model.predict_proba(row)[0, 1])
return {"churn_probability": score, "model_version": MODEL_VERSION}
Loading the model inside the request handler, or returning predictions with no model version attached.
Hold the line: the latency budget exists for a reason; don't ship something that breaks it.
Size the gain: is the offline improvement likely to matter to users or the business?
Options: optimise or compress the model, cache or precompute, distil into a smaller model, or run it only where it matters.
"I'd start by agreeing with the data scientist on what the gain is worth, because a small offline lift may not show up in any business metric. Then I'd be clear the latency budget isn't mine to bend; it's there because a slow response hurts the product. But I don't want to just say no. We'd profile the model and try the cheap wins first: an optimised runtime, quantisation, trimming features that are expensive to fetch but add little. If it's still too slow, there are design options. Distil it into a smaller model, precompute its scores in batch where inputs allow, or use a two-stage setup where a fast model handles most requests and the big one only scores the uncertain cases. Whatever we try, I'd test it in shadow first to check real latency before any user sees it."
Either shipping it anyway because it's more accurate, or rejecting it outright without exploring ways to keep the gain.
Measure first: break latency into network, feature lookup, preprocessing, inference and queueing.
Common tail causes: cold starts, garbage collection, slow feature store calls, queueing under bursts, very large inputs.
Levers: warm-up, caching, parallel feature fetches, dynamic batching, a smaller or quantised model, better autoscaling.
"First I'd trace one request end to end and split the time: network, fetching features, preprocessing, the model call and time spent waiting in a queue. The tail is often not the model at all. Common culprits are a feature store call that sometimes times out, a new instance serving its first requests before it's warmed up, or a queue building when traffic bursts faster than autoscaling reacts. Then I'd fix what the trace shows. For cold starts, send warm-up requests before marking the instance ready. For slow lookups, fetch features in parallel, cache hot entities and set tight timeouts with a fallback. If the model itself is slow, I'd try exporting to an optimised runtime, quantising, or distilling to a smaller model, and check with the data scientist that accuracy holds. For bursts, keep some headroom and scale on queue depth rather than CPU."
Jumping straight to bigger machines or a GPU without measuring where the time actually goes.
Visibility: cost per team, per job, per model and per thousand predictions.
Waste: idle GPUs, low utilisation, oversized instances, forgotten notebooks and endpoints.
Training: spot or preemptible capacity with checkpointing, mixed precision, fewer wasted sweeps.
Serving: batching, quantisation or smaller models, autoscaling to zero where allowed, CPU for small models.
"First I'd make the spend visible: tag everything by team, model and job, and work out cost per training run and per thousand predictions. Usually a few things dominate. Then I look for waste, which is often the biggest win: GPUs sitting idle, dev notebooks left running, endpoints with no traffic, and jobs showing low GPU utilisation because they're starved by data loading. For training, I'd move fault-tolerant jobs to spot or preemptible capacity with regular checkpoints, use mixed precision, and cut hyperparameter sweeps with early stopping. For serving, I'd check whether small models even need a GPU, add batching, try quantised or distilled models with the data scientists, and set autoscaling so endpoints scale down at quiet times. I'd track each change against quality and latency, so savings don't quietly cost accuracy."
Proposing only to buy cheaper instances, without measuring utilisation or cost per prediction.
Definition: the model sees features at serving time that are computed or distributed differently from training.
Sources: two code paths for one feature, different data sources, time windows, defaults for missing values.
Prevention: one feature definition used by both paths, logging served features for training, comparing distributions.
"Training-serving skew is when the features a model gets in production don't match what it saw in training, even for the same real-world input. The classic cause is two implementations: a data scientist computes a feature in SQL for training, and an engineer rewrites it in the service code. Small differences creep in, like a different time window, a timezone, or how nulls are filled. To prevent it, I make sure each feature has one definition that both training and serving use, either through a feature store or a shared library. Even better, I log the exact features the service used at prediction time and build the next training set from those logs, so training and serving literally share the data. Then I still monitor by comparing served feature distributions with training ones, as a safety net."
Treating skew as the same thing as data drift, or relying only on noticing a drop in accuracy after launch.
Problem: features rebuilt in every project, and computed differently for training and serving.
Offline store: full history in a warehouse or lake, for building point-in-time training sets.
Online store: the latest value per entity in a fast key-value store, for low-latency lookups at serving.
"A feature store solves two problems. Teams keep rebuilding the same features, like a customer's spend over the last 30 days, in slightly different ways. And a feature gets computed one way for training and another way in the live service, which causes skew. A feature store gives each feature one definition, registered once and reused. It usually has two storage layers because the access patterns are opposite. Training needs history: what was this customer's value at every point in the past, for millions of rows, and a warehouse or data lake is good at that. Serving needs the latest value for one customer in a few milliseconds, and a key-value store like Redis is good at that. The feature store keeps both in sync from the same definition, so the model sees the same logic in both places."
Describing a feature store as just a database table of features, with no mention of consistency between training and serving.
Problem: joining the latest feature value to old labels uses information that didn't exist at prediction time.
Fix: for each labelled event, take the most recent feature value strictly before the event time.
Symptom: great offline metrics that collapse in production.
"When I build training data, each label has a timestamp, the moment I would have made a prediction. A point-in-time join attaches, for each of those events, the feature values as they were just before that moment. If I instead join today's feature values, I leak the future. Say a feature is number of chargebacks on the account. Today's value already includes the chargeback from the fraud I'm trying to predict, so the model learns a shortcut that won't exist live. Offline metrics look amazing and production is disappointing. Feature stores do this join for me when I give them entity IDs and event timestamps. In pandas I can do it with merge_asof, looking backward only. I also account for when a value actually became available, because a feature computed by a nightly job isn't usable until that job finishes."
import pandas as pd
labels = labels.sort_values("event_time")
features = features.sort_values("feature_time")
train = pd.merge_asof(
labels, features,
left_on="event_time", right_on="feature_time",
by="user_id",
direction="backward", # only values from the past
allow_exact_matches=False, # strictly before the event
)
Joining on entity ID alone and taking the latest feature value, without thinking about timestamps.
Tracking: each run logs parameters, metrics, artifacts and code version, so experiments can be compared.
Registry: a named model with numbered versions, each pointing back to the run that produced it.
Promotion: aliases or tags like champion mark which version serving should load.
"Tracking is the lab notebook. Every training run logs its parameters, metrics, artifacts like the model file and plots, and ideally the code commit and data version. That lets me compare dozens of runs and see what actually helped. The registry is the catalogue of models that matter. I register a model under a name, and each time I register a new one it gets the next version number, linked to the run that produced it, so I can always trace a production model back to its exact training run. Then I mark which version is approved. Older setups used fixed stages like Staging and Production. Recent MLflow versions push you towards aliases, like champion and challenger, plus tags. My serving code loads the model by alias, so promoting or rolling back is just moving the alias, with no code change."
import mlflow
# Serving loads whatever version currently holds the alias
model = mlflow.pyfunc.load_model("models:/churn-model@champion")
Saying the registry and tracking are the same thing, or hard-coding a model file path in the serving code.
Don't copy into Git: large data goes in object storage or a table format; Git holds pointers or hashes.
Options: a data versioning tool that stores hashes in Git, table formats with snapshots and time travel, or immutable dated partitions.
Link it: every training run logs the data version it read, alongside the code commit.
"Data is too big for Git, so I version it by reference. There are a few ways I've seen work. One is a data versioning tool like DVC, where the files sit in object storage and Git only tracks small pointer files with content hashes, so checking out a commit and pulling gets you the matching data. Another is a table format with snapshots, like Delta Lake or Iceberg, where I can read a table as of a specific version or timestamp. The simplest is immutable, dated folders that nobody overwrites. Whatever the method, the key part is linking it: each training run logs the data version or snapshot ID next to the code commit and parameters. Then any model in the registry can be traced to exactly what it learned from. I also watch retention, because a snapshot that gets cleaned up breaks that link."
Saying the data doesn't need versioning because the query that built it is in Git, when the underlying tables keep changing.
Sources: random seeds, data shuffling and splits, parallel data loading, nondeterministic GPU kernels, library versions.
Controls: fix every seed, pin versions in a container, log the data snapshot, turn on deterministic modes where offered.
Realistic goal: exact rebuilds where it matters, otherwise results within a known tolerance.
"There are several sources. Random seeds for weight initialisation, shuffling and the train-test split, if they're not fixed. Data loading with several workers, where each worker needs its own seeded random state for augmentation. On GPUs, some operations use parallel reductions where the order of floating point additions varies, so results differ in the last digits and that can snowball over a long run. And library or driver versions, which can change numerics. To tighten it, I set seeds for every library involved, fix the split by saving it or hashing IDs, pin the whole environment in a container, record the data snapshot, and use the framework's deterministic settings, knowing they can slow training. In practice I aim for bit-exact rebuilds only where an audit requires it. Otherwise I define an acceptable tolerance on metrics and check reruns land inside it."
Claiming that setting one random seed makes GPU training fully reproducible.
CI: tests the pipeline code: unit tests on feature logic, schema checks, a small end-to-end training run.
CT: the pipeline runs again on new data, on a schedule or a trigger, and produces a candidate model.
CD: ships the pipeline itself, and ships a candidate model only after evaluation gates pass.
"In normal software, CI tests code and CD ships it. In ML there are two things being delivered: the training pipeline, which is code, and the models that pipeline keeps producing. CI covers the code side. I run unit tests on feature transformations, check schemas, and do a fast training run on a small sample to prove the pipeline works end to end. CD deploys that pipeline to the production orchestrator. Continuous training is the extra loop: the deployed pipeline reruns on fresh data, on a schedule or when monitoring triggers it, and outputs a candidate model. That candidate goes through its own gates: validation of the new data, evaluation against the current model on a fixed test set and on important segments, and checks on latency and size. Only if those pass is it registered and rolled out."
Describing continuous training as simply retraining on a cron job with no evaluation gate before deployment.
Code: unit tests for feature logic, including nulls, empty inputs and edge values.
Data: schema, ranges, null rates, row counts and freshness checked before training starts.
Model: a smoke run on a small sample, a minimum quality bar, behaviour checks on known cases, segment metrics.
"I think in three layers. Code tests are the normal ones: feature functions tested with nulls, empty frames, unexpected categories and extreme values. Data tests run before training: is the schema what I expect, are the row counts and null rates in a sane range, is the data fresh, did a column suddenly become constant. These catch the upstream breakages that cause most real failures. Model tests run after training: a quick end-to-end run on a small sample in CI to prove the pipeline works, then on the real run a minimum metric bar, comparison against the current production model, metrics for key segments so an average doesn't hide a regression, and a few behaviour checks, like a prediction that shouldn't change when an irrelevant field changes. I also test that the saved model loads and gives identical outputs in the serving environment."
Only testing that the training script runs without errors, with no checks on the data or the model's quality.
Fair comparison: both models scored on the same frozen test set.
Checks: a real overall gain, no big loss on any key segment, latency and size limits.
Explainable: return the reasons, so a blocked model tells people why.
"The gate takes metrics for the candidate and the current champion, both computed on the same frozen test set, because comparing numbers from different data means nothing. It requires a real gain overall, not just noise, so there's a minimum improvement. Then it checks each important segment, because a model can improve the average while getting worse for, say, new users. It enforces a latency limit so an accurate but slow model can't slip through. It returns the reasons rather than a bare false, so when a pipeline blocks a model the team can see why straight away. In the pipeline, a pass registers the model and moves it to a canary, and a fail stops the run and alerts the owner."
def should_promote(cand, champ, min_gain=0.005, max_drop=0.01, max_p95_ms=50):
"""Both metric dicts come from the same frozen test set."""
reasons = []
if cand["auc"] < champ["auc"] + min_gain:
reasons.append("no clear gain over the champion")
for seg, champ_auc in champ["segment_auc"].items():
cand_auc = cand["segment_auc"].get(seg)
if cand_auc is None or cand_auc < champ_auc - max_drop:
reasons.append(f"worse on segment {seg}")
if cand["p95_latency_ms"] > max_p95_ms:
reasons.append("too slow for the latency budget")
return len(reasons) == 0, reasons
Comparing metrics computed on different test sets, or promoting on a single overall number with no segment or latency checks.
Inputs: triggers, fresh labelled data with a label delay, data validation before training.
Train and evaluate: point-in-time training set, tracked run, comparison with the champion on a fixed test set and a recent window.
Release: register, shadow or canary, watch live metrics, then promote or roll back automatically.
Operate: lineage for every step, alerts on failures, a manual override.
"For fraud, labels arrive late, because chargebacks can take weeks, so I only train on transactions old enough to have settled labels. A trigger starts the run: a schedule, a drift alert, or a drop in measured precision. Step one validates the new data: schema, volumes, fraud rate in a sane range. If that fails, the pipeline stops and alerts. Step two builds a point-in-time correct training set from the feature store and trains, logging everything to the tracking server. Step three evaluates against the current champion on a fixed test set and on the most recent labelled window, including key segments and latency. If it passes, it's registered and deployed in shadow, scoring live traffic without acting. After a few days of comparing, it goes to a small canary, then full traffic, with automatic rollback if alert rates or latency go out of bounds."
Retraining straight into production with no validation, no comparison against the current model and no rollback path.
Before: the manual process and the pain it caused.
Priorities: what you automated first and why.
Result: how it changed release speed or reliability.
Lesson: what you'd do differently.
"In my last role, a pricing model was retrained every month by one data scientist running notebooks on her laptop, then emailing a file to the backend team. Releases took about a week and once went out with the wrong preprocessing. I didn't try to build everything at once. First I moved the notebook logic into a versioned training script and logged every run to MLflow, so we could reproduce a model. Next I registered models and changed the service to load by alias, which removed the emailed files. Then I added an orchestrated monthly pipeline with data checks and a comparison against the current model. A release went from about a week to under a day, and nobody had to remember steps. The lesson was to involve the data scientist early; she found issues in my version of her feature code that I'd have missed."
A story that jumps straight to a heavy platform with no sense of what was painful or what to automate first.
Input side: data quality and feature drift against the training reference.
Output side: prediction distribution, score shift, share of each class, confidence.
Proxies and late truth: early business signals now, true metrics once labels arrive, joined by prediction ID.
"Without labels I watch everything around the model. On the input side, data quality first: missing values, new categories, values out of range, broken pipelines. Then drift: how far each important feature's distribution has moved from the training data. On the output side, I track the distribution of predictions. If a model that usually flags two in a hundred suddenly flags ten, something changed, even if I can't say it's wrong yet. Then I look for early proxies from the business, like how many flagged cases a review team confirms, or click-through for a ranking model. And I log every prediction with an ID, so when the real outcomes arrive weeks later I join them back and compute the true metrics by cohort. Drift alone doesn't prove the model is worse, so I treat it as a reason to look, not an automatic verdict."
Saying there's nothing to monitor until labels arrive, or treating any drift alert as proof the model is broken.
Bins from the reference: quantile edges on the training data, so each bin starts roughly equal.
Compare shares: fraction of reference and current data in each bin; guard against zero.
Read it: sum of (actual minus expected) times log(actual over expected); higher means more shift.
"I take the bin edges from the reference data, usually the training set, using quantiles so each bin holds about the same share. I clip the current data into the reference range, so values beyond it land in the end bins rather than being dropped. Then I compute the share of each dataset in each bin, floor them with a tiny number so an empty bin doesn't give a log of zero, and sum the difference times the log ratio. A common rule of thumb is that below 0.1 is little change, 0.1 to 0.25 is worth a look, and above 0.25 is a big shift. I treat those as starting points and tune thresholds per feature, because with huge samples tiny, harmless shifts can still register, and I care more about drift in the features the model relies on most."
import numpy as np
def psi(expected, actual, bins=10, eps=1e-6):
edges = np.unique(np.quantile(expected, np.linspace(0, 1, bins + 1)))
# values outside the reference range go into the end bins
actual = np.clip(actual, edges[0], edges[-1])
e = np.histogram(expected, bins=edges)[0] / len(expected)
a = np.histogram(actual, bins=edges)[0] / len(actual)
e = np.clip(e, eps, None)
a = np.clip(a, eps, None)
return float(np.sum((a - e) * np.log(a / e)))
Recomputing bin edges from the current data each time, or not handling empty bins and getting infinite values.
Service: request rate, error rate, latency percentiles, resource use.
Data: schema violations, missing values, feature drift.
Model: prediction distribution, and quality metrics once labels arrive.
Business: the outcome the model exists to move.
"I'd build it in four layers, because each one catches a different failure. The service layer is the same as any API: requests per second, error rate, latency at the median and the tail, and CPU, memory or GPU use. That tells me it's up. The data layer checks what's coming in: schema violations, missing value rates and drift on the important features compared with training. The model layer tracks the distribution of predictions and, once true outcomes arrive, accuracy or precision and recall, split by key segments. The business layer shows the thing the model is supposed to improve, like fraud losses caught or conversion. Every chart shows which model version was live, so I can connect a change in the numbers with a deployment. Alerts go on a few of these, not all, to avoid noise."
Listing only uptime, latency and errors, as if the model were any other web service.
Schedule: simple and predictable; fits when change is steady and retraining is cheap.
Drift trigger: reacts to input change, but drift doesn't always mean worse predictions.
Performance trigger: most direct, but needs labels, which may be slow.
Blend: a schedule as the baseline, with triggers that can start a run early.
"It depends on how fast the domain moves and how quickly I get labels back. A schedule is the simplest: retrain weekly or monthly, every run goes through the same evaluation gate, and it's easy to plan. It works well when change is gradual. A drift trigger reacts faster when inputs change, but drift isn't the same as a worse model, so I'd use it to start an evaluation, not to push a new model blindly. A performance trigger is the most direct signal, but it only works when labels come back reasonably fast. In practice I blend them: a schedule as the baseline, plus triggers on big drift or a measured drop that can start a run early. Whatever starts it, the new model still has to beat the current one before it ships."
Saying you'd retrain as often as possible, as if a fresh model is automatically a better one.
Diagnose: which features fire, how big the shifts are, and whether any ever came with a quality drop.
Retune: thresholds per feature, weighted by importance; seasonal baselines; sustained shift, not one day.
Split: page only on what hurts predictions; the rest goes to a weekly report.
"An alert people ignore is worse than none, because the real one gets missed too. First I'd look at the history: which features fire, by how much, and whether any alert ever lined up with a real drop in model quality. Usually a few things cause most of the noise, like a statistical test that flags tiny shifts because traffic is huge, or a feature with a normal weekly pattern compared against a flat baseline. Then I'd retune. Thresholds per feature, stricter for the features the model relies on most. A baseline that respects seasonality, like the same weekday last month. And require a shift to last before alerting. Finally I'd split the outputs: page someone only for drift on important features or a drop in prediction quality, and send the rest to a weekly report that someone owns and reviews."
Just raising every threshold until the alerts stop, or switching the monitor off.
Situation: the model, what it did, and what went wrong.
Detection: how it was noticed, and how long it had been happening.
Fix and root cause: what you did in the moment and what actually caused it.
Lasting change: the check or process you added so it's caught fast next time.
"At my last company we had a demand forecasting model feeding stock orders. Planners told us its forecasts for one product category had looked odd for about two weeks. We had service monitoring, but nothing on the data. When I dug in, an upstream team had changed a category code, so a whole group of products started arriving with an unknown category. Our pipeline filled it with a default, and the model treated them like a much smaller category. I fixed the mapping, backfilled the features and retrained, and forecasts came back in line. The bigger change was stopping it happening silently again. I added data validation before both training and scoring, alerting on unseen categories and jumps in default values, and we agreed a schema contract with the upstream team so changes get announced first."
A story where the fix was a one-off patch with nothing added to stop it happening again, or blaming the other team entirely.
Shadow: gets a copy of live traffic, predictions logged but not used; proves it runs and shows how it differs, at zero user risk.
Canary: a small slice of real traffic uses it; catches operational problems while limiting impact.
A/B test: a planned random split measuring a business metric; answers whether it's actually better.
"They answer different questions, so I often use them in order. In shadow mode the new model gets a copy of live requests, but its predictions are only logged. Users never see them. That tells me it handles real traffic, what its latency is, and how often it disagrees with the current model, with no user risk. It can't tell me the business effect, because nothing acts on its output. A canary sends a small slice of real traffic to the new model and watches errors, latency and key metrics. It's a safety step: if something is off, only a few users are hit and I roll back. An A/B test is an experiment: a random split, a chosen metric, a planned sample size and duration. That's what tells me the new model actually improves the outcome, beyond just not breaking."
Treating a canary as proof the model is better, or saying shadow mode measures business impact.
Act: roll back to the previous version quickly, then confirm the metric recovers.
Communicate: tell the owners what happened and what you did.
Investigate: compare predictions, inputs and segments between the two versions.
Prepared ahead: the previous version kept warm, rollback as a one-step action, a canary stage.
"If the timing lines up with the release, I roll back first and investigate second. The previous version should still be deployable in one step, like moving the registry alias back, so this takes minutes. I'd check the metric starts recovering, and post in the incident channel what we saw and what we did. Then I dig in: compare the two versions' predictions on the same traffic, look at which segments moved, and check whether the new model got different inputs than it was tested on, like a feature that's null in production. It's also worth confirming it really was the model and not another release at the same time. Afterwards I'd ask why the rollout let this reach everyone. The fix is usually a canary stage with automatic rollback on that metric, so next time it hits a small slice."
Spending hours debugging the new model in production while the metric keeps falling, instead of rolling back first.
Generation: output comes token by token, so cost and latency depend on output length.
Memory: weights plus a KV cache that grows with context length and concurrent requests.
Batching: continuous batching and paged KV cache to keep the GPU busy.
Metrics: time to first token, time per output token, tokens per second, cost per request.
"A classic model does one forward pass per request, cheap and predictable. An LLM generates one token at a time, so a long answer takes many passes and latency depends on how much it writes. There are two phases: prefill, which processes the whole prompt at once and is compute heavy, and decoding, which is usually limited by memory bandwidth. Memory is the big constraint. Besides the weights, each request keeps a KV cache, the stored attention keys and values for every token so far, which grows with context length, so long prompts and many concurrent users can run out of GPU memory. That's why serving engines use continuous batching, where requests join and leave the batch between tokens, and manage the cache in pages. I'd also look at quantisation to fit the model on fewer GPUs, and I'd measure time to first token and time per output token, not just one latency number."
Treating an LLM like any other model behind an API and quoting one latency figure with no mention of tokens, memory or batching.
Eval set: versioned cases from real traffic and known failures, each with expected behaviour.
Scoring: exact checks where possible, rubric grading by a judge model checked against human labels, plus latency and cost.
Gate: runs in CI on every change; blocks on regressions per category, not just the average.
Feedback loop: production failures become new cases.
"I treat a prompt, a model version and retrieval settings like code: versioned, and every change goes through CI. The core is an eval set of a few hundred cases, drawn from real, anonymised traffic, plus every failure we've seen, each tagged by category and with what a good answer must contain or avoid. Scoring uses deterministic checks where I can: valid JSON, required fields, a citation present, no banned content. For open-ended quality I use a judge model with a clear rubric, and I check its grades against a sample of human labels, because a judge can be biased. The pipeline also records latency and token cost. The gate compares against the current version per category, so a gain in one area can't hide a regression in another. After release, flagged production answers get reviewed and added to the set."
Evaluating prompt changes by trying a few examples by hand, or trusting a judge model's scores without ever checking them against people.
Cost and speed: tokens in and out per request, time to first token, spend per feature or customer.
Quality: sampled grading, groundedness against retrieved context, user feedback, refusal rate.
Safety: guardrail triggers, prompt injection attempts, leaks of personal data.
Traceability: every call logged with prompt version, model version and retrieved documents.
"A few things are new. Cost scales with tokens, so I track input and output tokens per request and spend per feature, and alert when a prompt change or a runaway loop makes it jump. Latency splits into time to first token and total generation time. Quality can't be read off one number, so I sample live conversations and grade them, with a judge model checked against people, and I track signals like thumbs down, retries and refusals. For retrieval features I check whether answers are grounded in the documents fetched. Safety gets its own counters: guardrail blocks, injection attempts, any personal data showing up in output. And every call is logged with the prompt version, model version and retrieved context, within the privacy rules we follow, so when something goes wrong I can reproduce exactly what the model saw."
Monitoring only uptime and latency, with no view of token cost, output quality or which prompt version produced an answer.
ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.