Visual walkthrough — Model serving frameworks (TorchServe, Triton)
Step 1 — What is one prediction, drawn as a bar of time?
WHAT. When a client sends a model one input (say, one photo to classify), the server spends some amount of time before it can reply. Let us just draw that time as a horizontal bar — length = seconds.
WHY. Before we can talk about "faster", we need a picture of time itself. Everything below is just cutting and stacking these bars. If we can see time as length, "speedup" becomes "shorter bar", which anyone can eyeball.
PICTURE. Look at the figure. The bar splits into two coloured chunks, because real inference time is genuinely two different things glued together:
- The orange chunk = fixed overhead. This is the setup tax the GPU pays every time you press "go": launching the compute kernel, copying data from CPU memory to GPU memory, framework bookkeeping. It does not depend on how much data you send — it is a doorway you must walk through even to compute a single number. (See GPU architecture & kernel launch overhead for why this doorway exists.)
- The blue chunk = marginal compute per sample. This is the actual arithmetic for one input. Send two inputs and you do this arithmetic twice.

Step 2 — Serving requests the naive way (one taxi each)
WHAT. Now clients each send one request. "" is just how many requests — a whole number like . The naive server handles them one after another: finish request 1 completely, then start request 2, and so on.
WHY. This is the Flask + `model.predict()` behaviour the parent warned about. We derive its cost so we have something to beat. You cannot prove batching helps until you know the number it must improve on.
PICTURE. identical bars laid end to end. Crucially, every bar carries its own orange doorway . You pay the setup tax separate times — that repeated orange is the waste we will hunt down.
Read it as: " copies of (one doorway + one unit of work)."

Step 3 — The trick: one doorway for the whole crowd
WHAT. Instead of separate launches, we stack all inputs into a single block and push them through the GPU in one forward pass. This block-of-inputs is called a batch; is its batch size.
WHY. A GPU has thousands of tiny arithmetic units sitting side by side. Processing 1 sample uses a sliver of them; the rest idle. Feeding it samples at once puts those idle units to work during the same launch — so we walk through the orange doorway only once for the whole crowd. (The parent's slogan: a GPU is a bus, not a taxi.)
PICTURE. One orange doorway at the front, then blue blocks of work stacked after it. Compare directly to Step 2: the extra orange doorways vanished.
"One doorway, then units of arithmetic."

Step 4 — Line the two bars up and read off the speedup
WHAT. We define speedup = old time ÷ new time. Bigger = better.
WHY. We chose division (not subtraction) on purpose: it answers "how many times faster?", a scale-free number you can compare across models. Subtraction would give raw milliseconds, which mean nothing without context.
PICTURE. The two bars from Steps 2 and 3 stacked vertically, same starting edge. The ratio of their lengths is .
- Numerator : the long naive bar.
- Denominator : the short batched bar.
- Their gap (the deleted oranges) is what measures.

Step 5 — Push to infinity: the ceiling
WHAT. What happens if we make the batch enormous? We take the limit as .
WHY a limit? A limit answers "where is this heading if I keep going?" We use it because managers ask "so should I just crank batch size forever?" — and the honest answer is a single ceiling number, which only a limit reveals.
PICTURE. A curve of against : it climbs steeply, then flattens against a horizontal dashed line. To find that line, divide top and bottom by (letting the tiny terms fade to nothing):
- : the shared doorway, split across ever-more samples, becomes negligible per sample.
- What remains, , is the hard ceiling: no batch size can beat it.

Step 6 — The two extreme regimes (why some models love batching and some shrug)
WHAT. The ceiling behaves totally differently in two worlds:
- Overhead-bound (): tiny cheap model, fat doorway. Ceiling is huge.
- Compute-bound (): giant LLM layer, thin doorway. Ceiling .
WHY. These are the degenerate cases the parent listed — the edges where the general formula collapses into a slogan. Covering both means you'll never be surprised in production.
PICTURE. Two speedup curves on one axis: a blue one ( big) shooting up to a high ceiling, an orange one ( big) hugging the value 1 almost flat.

Step 7 — Where does the batch come from? The queue-and-timer
WHAT. Real requests don't all arrive together. The server waits a little, collecting arrivals into a batch, then fires. Two rules end the wait: reach max_batch_size, or hit max_queue_delay — whichever comes first.
WHY. Batching only pays off if a batch actually forms. This waiting adds a new time cost we hadn't drawn: the queue wait . Ignoring it is exactly the p99 trap the parent warned about.
PICTURE. A timeline: requests drop in as dots; a timer bar counts up to max_queue_delay; when it fires (or the box fills), the collected dots leave together as one batch. The full per-request latency is now three stacked pieces:

Step 8 — The throughput view, and stacking instances
WHAT. Throughput = how many requests finish per second. One batched instance clears requests every seconds:
Run concurrent instances (separate model copies) and multiply by until the GPU runs out of memory or compute.
WHY. Latency (Steps 1–7) is "how long one request waits"; throughput is "how many the system clears". They are different questions and the tradeoff between them is the whole art of serving. Concurrency () and batching () are orthogonal — different axes you tune together.
PICTURE. Two dials side by side: a batching dial (one copy, wider inputs) and a concurrency dial ( parallel copies). Total throughput .

The one-picture summary

The final figure compresses the whole journey: (top) serial bars each with its own orange doorway → (middle) one batched bar with a single doorway → (right) the speedup curve rising to its ceiling , with the queue-wait tucked in front. Everything you tuned lives on this one canvas.
Recall Feynman retelling — say it back in plain words
Every prediction costs a fixed doorway fee () plus a bit of actual work per item (). If you serve people one at a time, everyone pays the doorway fee — huge waste. So the server waits a few milliseconds, gathers a crowd of people, and walks them through the door together: one fee, then helpings of work. That makes things times faster. But you can't win forever — as the crowd grows, the shared fee-per-person shrinks toward zero and the speedup flattens at . Cheap models (fat doorway) win big; giant models (tiny doorway, heavy work) barely notice. And gathering the crowd means people wait in line (), which fattens the tail latency — so batch size is a dial, not a free lunch. Want more raw throughput on top? Run several copies of the kitchen at once (concurrency ).
Recall
What are the two parts of one request's inference time? ::: A fixed overhead (paid once per launch) and a marginal compute (per sample). Why is serial serving wasteful? ::: It pays the fixed overhead once for every request ( total) instead of once per batch. What is the batching speedup formula and its limit? ::: , with . When does batching barely help? ::: When compute dominates (), so the ceiling . How is batching different from concurrency? ::: Batching = one model copy processing many inputs in one pass; concurrency = separate copies running in parallel. What third time-cost appears once you form batches from a live stream? ::: The queue wait while requests accumulate — it inflates p99 latency.
See also: ONNX & model interchange · A-B testing & model versioning · back to parent.