MLOps & Deployment
Time limit: 90 minutes Total marks: 60 Instructions: Answer all questions. Show all derivations, code, and reasoning. Numeric answers must be justified. Partial credit is awarded for correct method.
Question 1 — Inference Serving: Latency, Throughput & Cost (24 marks)
You are deploying a quantized LLM behind a real-time REST API (FastAPI + a vLLM backend). The system must satisfy a p99 latency SLO of 500 ms at a target load.
Assume the serving node processes requests via continuous batching. Each request's total service time is modelled as:
where ms (prefill + network), output tokens, and the per-request decode rate under batch size is
(a) Derive as a function of and find the largest batch size that still meets the 500 ms latency bound (assume every request in the batch decodes concurrently). (5)
(b) The arrival process is Poisson with rate requests/s. Model one serving replica as an M/M/1 queue whose service rate is (requests/s). Give the formula for expected total response time (queue + service), and find the maximum per replica such that ms. State the utilisation at that point. (7)
(c) You must serve a sustained load of req/s. Using the per-replica capacity from (b), how many replicas are required? If each GPU replica costs $2.60/hour, compute the hourly serving cost and the cost per million requests at full load. (5)
(d) Post-quantization the model uses INT8 weights. Explain quantitatively why quantization can reduce both memory footprint and latency, and derive the theoretical memory saving factor when moving from FP16 to INT8 weights for a model with parameters. Then discuss one accuracy-preservation technique and one monitoring signal you would track to detect quantization-induced degradation in production. (7)
Question 2 — Data Drift Detection & Retraining Trigger (20 marks)
A production classifier scores incoming feature . During training, the feature followed . In production over a monitoring window you observe .
(a) The population stability index between two Gaussians via KL divergence is used as a drift score. Show that
with . Then compute numerically for the observed . (8)
(b) Distinguish data drift from concept drift with a precise probabilistic definition of each ( vs ). Give a concrete example where accuracy stays high despite large data drift, and one where accuracy collapses with no input drift. (6)
(c) You configure an automated retraining pipeline. Define a statistically principled trigger: given a per-window drift statistic, explain how you would set the threshold to control false-positive retraining rate, and describe the CI/CD steps that must run automatically between "drift detected" and "new model serving live traffic." Include how versioning (data + model + registry) guarantees reproducibility of a rollback. (6)
Question 3 — Deployment Architecture & A/B Rollout (16 marks)
(a) Contrast batch vs real-time inference across: latency SLO, cost model, and freshness of features. State one workload best suited to each. (4)
(b) You roll out model B against incumbent A using a 50/50 A/B test. Baseline conversion (A) = 4.0%. You want to detect a lift to 4.4% (absolute) with power 0.8 at (two-sided). Using the two-proportion sample-size approximation
with , compute the required samples per arm. (6)
(c) Write a minimal FastAPI endpoint that (i) loads a model at startup, (ii) routes 10% of traffic to a canary model by a hash of the request id, and (iii) returns a prediction with the serving model version in the response. Explain why hashing the request id (not random routing) is important for consistency. (6)
Answer keyMark scheme & solutions
Question 1
(a) [5 marks] tokens/s per request.
(2 marks derivation)
Set : . (2) Largest integer batch: . (1) Then s.
(b) [7 marks] Service rate req/s. (1)
For M/M/1, expected response time (Little/queueing result):
Require : req/s. (3)
Utilisation . (1)
Interpretation credit: Since service alone (0.488 s) nearly consumes the 500 ms budget, almost no queueing headroom remains — hence the tiny per-replica . This is the correct, if uncomfortable, conclusion. (Grader: full marks for correct method even noting the tightness.)
(c) [5 marks] Per-replica capacity req/s. Hourly cost = 2449 \times \2.60 = $6367.4/\text{h}= 120 \times 3600 = 432{,}000= \dfrac{6367.4}{0.432} = $14{,}739$ per million. (2)
(Discussion note: such a strict p99≈service-time budget makes real-time serving expensive; relaxing the SLO or batching harder trades latency for cost. Recognising this earns full marks.)
(d) [7 marks]
- Memory: FP16 = 2 bytes/param, INT8 = 1 byte/param. Footprint = bytes. Saving factor (weights halve). (2)
- Latency: LLM decode is memory-bandwidth bound; time to stream weights bytes moved. Halving weight bytes roughly halves the per-token weight-load time, and INT8 tensor-core throughput is higher than FP16, so latency drops (bandwidth + compute). (2)
- Accuracy preservation: e.g. per-channel / group-wise quantization, calibration on representative data, or keeping outlier channels in higher precision (SmoothQuant / AWQ). Naming any valid technique with reason. (1.5)
- Monitoring signal: track output quality / perplexity drift or downstream task metric, plus token-level distribution shift vs FP16 baseline, or user thumbs-down rate. (1.5)
Question 2
(a) [8 marks] KL between two Gaussians , : Take : , and . (4)
Numeric with :
(b) [6 marks]
- Data drift (covariate shift): input distribution changes, , while unchanged. (1.5)
- Concept drift: the mapping changes, . (1.5)
- High accuracy despite drift: input distribution shifts into a region where the decision boundary is still correct (e.g. more traffic from a demographic the model already handles well). (1.5)
- Accuracy collapse with no input drift: same but labels' relationship changed — e.g. fraud patterns evolve, spam wording stays but "spam vs not" definition flips. (1.5)
(c) [6 marks]
- Threshold via null distribution: estimate the drift statistic's distribution under "no drift" (bootstrap over historical stable windows). Set threshold at a quantile controlling per-window false-positive rate ; apply multiple-comparison / windowing correction to bound the expected number of false retrains. (2)
- Automated CI/CD chain: drift alert → trigger retraining job on versioned data snapshot → automated eval on held-out + shadow traffic → gate on metric threshold → register new model in registry with lineage → canary deploy → promote. (2)
- Versioning for rollback: DVC pins the exact dataset hash, MLflow/registry pins model artifact + code commit + hyperparams; a rollback = redeploy the previous registry version, guaranteed reproducible because all three (data, code, model) are content-addressed. (2)
Question 3
(a) [4 marks]
| Batch | Real-time | |
|---|---|---|
| Latency SLO | minutes–hours (throughput-oriented) | ms (per-request) |
| Cost | cheap, spot/idle GPUs, amortised | expensive, always-on replicas |
| Feature freshness | stale (as of last batch) | fresh/online features |
Example: batch → nightly recommendation precompute; real-time → fraud scoring at checkout. (4: 1 per row + example)
(b) [6 marks] . (1) . Sum . (2) . (1)
(c) [6 marks]
import hashlib
from fastapi import FastAPI, Request
import joblib
app = FastAPI()
models = {}
@app.on_event("startup")
def load():
models["A"] = joblib.load("model_a.pkl") # stable
models["B"] = joblib.load("model_b.pkl") # canary
def route(req_id: str) -> str:
h = int(hashlib.md5(req_id.encode()).hexdigest(), 16)
return "B" if (h % 100) < 10 else "A" # 10% canary
@app.post("/predict")
async def predict(request: Request):
body = await request.json()
req_id = body["request_id"]
version = route(req_id)
pred = models[version].predict([body["features"]])[0]
return {"prediction": float(pred), "model_version": version}(4 marks: startup load, hash routing 10%, version in response)
Why hash the request id: deterministic routing means the same user/request always hits the same model — consistent user experience, valid A/B attribution, and no flip-flop between refreshes. Pure random routing would send repeat requests to different models, contaminating measurement and confusing users. (2)
[
{"claim":"Q1a largest batch size b*=14 gives Treq=0.488s <=0.5","code":"b=14; T=0.040+0.032*b; result=(T<=0.5) and (0.040+0.032*15>0.5)"},
{"claim":"Q1b lambda max = 1/0.488 - 2 approx 0.0492","code":"mu=1/0.488; lam=mu-2; result=abs(lam-0.04918)<1e-3"},
{"claim":"Q1c N=2449 replicas for 120 req/s","code":"import math; lam=1/0.488-2; N=math.ceil(120/lam); result=(N==2449)"},
{"claim":"Q2a KL(N(0.5,1.5^2)||N(0,1)) approx 0.34453","code":"kl=ln(Rational(1,1)/Rational(3,2))+(Rational(9,4)+Rational(1,4))/2-Rational(1,2); result=abs(float(kl)-0.344535)<1e-4"},
{"claim":"Q3b per-arm n approx 39427","code":"n=(7.84*(0.04*0.96+0.044*0.956))/(0.004**2); result=abs(n-39427)<50"}
]