Two patterns that look similar but aren't
Once you're past the single-model-single-request demo, two production patterns come up. They look related but optimize for different things, and using the wrong one wastes resources.
- Multiple models in one process — your service hosts several different models (e.g. a 7B for chat + a small embedder + a VLM for image understanding). Memory is shared at the process level; you save the per-Python-process overhead but you compete for unified memory between the models.
- Batched inference for one model — your service receives many concurrent prompts for the same model and processes them as a batch. Throughput goes up; per-request latency goes up too; the trade is for total compute efficiency.
This lesson covers both patterns and when each is the right call.
Multiple models — the memory accounting
Hosting two models in one process means both their weights live in unified memory simultaneously. The per-model napkin math from foundations.lesson4 just adds up. A 7B Q4 (~5 GB) + a small embedding model (~1 GB) + a 2B VLM (~2 GB) = ~8 GB resident, plus KV cache for whichever is currently generating. Accounting matters more here than in the single-model case because you can't afford either model to suddenly need its full inference budget.
The pattern that works: load all models at startup, keep them resident, route requests to the right model based on the request type. The pattern that doesn't work: load on demand and unload on idle — model load latency (often seconds for larger models) dwarfs inference latency for any individual request.
Batched inference — when throughput beats latency
If you have many concurrent prompts for the same model, batching them processes more tokens per GPU dispatch and improves throughput-per-dollar significantly. The trade is per-request latency: each request waits for the batch window to close before it sees first-token output.
mlx-lm doesn't have first-class batched-generation primitives in the way some PyTorch serving stacks do, so the practical pattern is async request collection at the service layer (FastAPI), then single-prompt inference per request via mlx-lm. For workloads dominated by many concurrent short prompts, look at higher-level serving stacks built on MLX (or wait for mlx-lm's batching APIs to mature).
The decision tree
- Single model, single user — straight FastAPI service from prod.lesson1, no batching needed.
- Multiple models, one process, low concurrency — load all at startup, route by request type, accept that you're spending unified memory on availability.
- Single model, very high concurrency — explore batched serving (Ollama or a custom batching wrapper); accept higher per-request latency for higher throughput.
- Multiple models AND high concurrency — usually a sign you're outgrowing single-Mac serving; evaluate whether a fleet of single-purpose Macs is more economical.