5.3.6 · D3MLOps & Deployment

Worked examples — Model serving (REST APIs, FastAPI)

3,148 words14 min readBack to topic

The scenario matrix

Think of an incoming HTTP request as a point that lands somewhere in a grid. The two axes that matter most are: is the input well-formed? and how many items are we predicting? Layered on top are degenerate inputs (empty, zero, extreme) and performance limits (latency, throughput).

# Case class What makes it different Worked in
A Happy single — valid, one row The "sunny day" path → 200 Ex 1
B Type-invalid input Wrong type (string where float expected) → 422 Ex 2
C Missing / extra field Schema shape wrong → 422 Ex 3
D Degenerate: empty batch rows = [] — valid JSON, zero work Ex 4
E Numpy leak Valid predict, but non-JSON return type → 500 Ex 5
F Latency budget Which stage dominates ? Ex 6
G Throughput / scaling Word problem: how many workers? Ex 7
H Batch vs N singles Same 1000 rows, two designs → cost tradeoff Ex 8
I Exam twist: cold start Model loaded per-request vs at startup Ex 9
J Extreme value / overflow Valid float but inf/NaN slips through Ex 10

Every cell A–J is covered below. Cells B, C, J are the "bad-input quadrants" — the equivalent of the tan-formula failing in quadrants II/III/IV: naive code gives the wrong status code, and we show the sign-based fix (which 4xx vs 5xx).


Example 1 — Case A: the happy single request


Example 2 — Case B: wrong type (the "quadrant II" of bad input)


Example 3 — Case C: missing / extra field


Example 4 — Case D: the degenerate empty batch


Example 5 — Case E: the numpy leak


Example 6 — Case F: which latency stage dominates?

Figure 1 — The five pipeline stages of one request, stacked into a single bar. Height of each coloured block = time that stage takes (labelled in ms). The two orange blocks are inference and serialize; the tall inference block (70 ms) is called out with an arrow as the "dominant stage." The total bar height reads T_total = 100 ms. Read it top-to-bottom: a request passes through each block in series, so total time is the sum of the block heights.


Example 7 — Case G: how many workers? (word problem)


Example 8 — Case H: batch endpoint vs N single calls

Figure 2 — Two stacked bars comparing designs for scoring 1000 rows. Left bar (Design A: 1000 separate requests) is dominated by a huge teal "overhead" block because the per-request overhead is paid 1000 times; its total is 10200 ms. Right bar (Design B: one batch request) is tiny — teal overhead paid once (10 ms) plus the same orange inference (200 ms), total 210 ms. The plum arrow labels the killed overhead. The visual gap (~48x shorter) is the whole argument for batching.


Example 9 — Case I: exam twist, the cold-start trap


Example 10 — Case J: extreme value / NaN leak


Coverage recap

Recall Did we hit every cell?

A (Ex 1), B (Ex 2), C (Ex 3), D (Ex 4), E (Ex 5), F (Ex 6), G (Ex 7), H (Ex 8), I (Ex 9), J (Ex 10). Every row of the matrix has a worked example. ✓

Recall Status-code decision rule

Bad input the client controls (wrong type, missing field, inf/NaN) ::: 4xx (usually 422) Something our code/model broke (numpy leak, unhandled exception) ::: 5xx (usually 500) Valid but empty input ::: 200 with an empty result

Related depth: Batching & Inference Optimization, Load Balancing & Autoscaling, Model Monitoring & Drift, gRPC vs REST, Docker & Containerization, CI-CD for ML.