Why a naive loop is not a server
Loading a model and calling generate in a loop works, and it will serve perhaps one user acceptably. The gap between that and a production server is larger than it appears, and it is mostly about how the accelerator is kept busy.
The underlying constraint is that generation is memory-bandwidth-bound, not compute-bound. Producing each token requires reading the entire model's weights from memory, and the arithmetic performed on them is trivial by comparison. So the accelerator spends most of its time waiting on memory rather than computing.
That has a consequence that drives everything else: processing one request uses a fraction of the available compute. If you read all the weights anyway, you may as well do the arithmetic for many requests at once, because the expensive part, the memory read, is shared.
So batching is not an optimisation, it is the whole game. A server that batches well can serve many times the throughput of one that does not, on identical hardware.
What a production inference server provides beyond a loop: batching, memory management for the key-value cache, request scheduling, streaming, and continuity when requests arrive and complete at different times. vLLM and SGLang are the common open implementations, and the inference internals themselves are covered in more depth in the existing LLM inference lesson.

