Skip to main content

08 · ML and LLM foundations

Serving models

Running a model on your own machines or through a provider, what really drives the cost, and how to share one model safely.

11 minCase study: Adapta (opens in new window) · docket (opens in new window)

A trained model is a large file of numbers, its weights. To use it, an application needs an server: a program that loads the weights into memory, takes requests and generates answers, fast and cheaply enough to be useful. It is mostly a classic infrastructure problem (capacity, many users at once, isolation, authentication) with a few quirks of its own.

This lesson covers five decisions: whether to run models yourself or use a hosted service, how to make a model fit in memory, what the engine that runs it does, what the common API offers when many clients share a server, and what drives cost and speed. Adapta is the server-side example; docket, the client-side one.

Run it yourself or use a hosted API

Hosted API Your own server
Models the most capable on the market open-weight models
Cost per token hardware and operations, fixed
Data leaves for the provider stays in your infrastructure
Operations close to none yours: GPUs, upgrades, monitoring
Customization whatever the provider offers full, including your own adapters

It is not a binary choice: many teams use a hosted model for hard tasks and a local one for high-volume or sensitive work.

Making the model fit

A model's size is counted in parameters, the individual numbers it learned. At 16 bits each, a 7-billion-parameter model needs about 14 GB of memory for the weights alone. stores each number with less precision, say 4 bits instead of 16, much like a compressed photo keeps most of the picture in a smaller file. The model uses less memory and runs faster, at a quality cost that depends on the model, the method and the task.

Two file formats come up often. , used by the popular llama.cpp engine, is a single file with the weights (often quantized) and everything needed to use them. is the usual format in the Hugging Face ecosystem, typically at full or half precision.

What the engine does

Generating text has two phases. First the engine reads the whole at once (prefill). Then it writes the answer one , a word or piece of a word, at a time (decode). To avoid reprocessing the prompt at every step, it keeps notes on what it has read: the KV cache. Those notes grow with the length of the context and with the number of requests in progress, which is why long contexts and many simultaneous users compete for the same memory.

  1. Promptthe whole request
  2. Prefillreads it all at once
  3. KV cachenotes on everything read so far
  4. Decodewrites one token

each new token joins the notes, and decode runs again

The prompt is read once; the answer comes out one token at a time. The notes in the middle are what fills memory.

One base model, many behaviors

A adapter is a small file applied on top of a base model to change its behavior, a bit like a lens on a camera. One base can then serve many behaviors, choosing the adapter per request. What this costs depends on the engine. In Adapta's llama.cpp backend, every combination of base and adapter is a full copy in memory. In vLLM, several adapters share one loaded base and can be served in the same batch, up to a configured limit.

In Adapta

  • llama.cpp backendone full copy per combination
    • Base + adapter A
    • Base + adapter B
    • Base + adapter C
  • vLLM backendone base, adapters added on demand
    • Shared base
    • Adapters A, B, C
Three fine-tuned endpoints cost three full models with llama.cpp, but one base plus three small files with vLLM. Source: Operations Runbook

The OpenAI-compatible API

OpenAI's chat completions request format has become the common language between applications and models; most servers and providers accept it. You can switch providers by changing an address and a model name. But "compatible" does not mean "equivalent". Support for tools, , streaming the answer as it is written, or cached-token reporting varies between servers.

Sharing a server

When several teams or customers share a server, a setup called , every call has to answer two questions: who is calling, and what may they reach. (docket, by contrast, is beta software for a single operator, with no tenants or quotas, per its README.)

Put together, a chat request to Adapta goes through these steps:

In Adapta

  1. Check the keyit must match the endpoint asked for
  2. Build the promptadds the project's documents, if any, cut to fit
  3. Generateone request at a time per model
  4. Count and replytokens recorded, answer returned
The serving path of a self-hosted model: access control first, then the engine. With llama.cpp, each loaded model handles one request at a time. Source: Architecture

What drives cost and speed

  • Output length. The prompt is read in parallel, but the answer is written one token at a time. Long answers dominate the wait, and providers usually charge more for output tokens.
  • Context size. More context means more work during prefill and a larger KV cache.
  • Batching. Grouping requests uses the GPU better, but a single request may wait longer.
  • Prompt caching. Many services reuse a stable, identical start of the prompt, such as system instructions and tool definitions, at a lower price.
  • Model size. The biggest factor. Hence : sending each task to the smallest model that handles it well.

Routing and are covered in depth in Context, routing and cost.

Putting it into practice

The central decision is which traffic justifies running your own inference.

  • Data that can't leave your infrastructure makes self-hosting a requirement rather than an option.
  • Steady, high volume pays for fixed capacity; bursty traffic is usually cheaper per token on a hosted API.
  • Someone has to operate it: the GPUs, the model upgrades and the endpoint's security. An unaudited server exposed to the network is a bigger risk than a hosted API.
  • Measure latency and quality with your own traffic before committing. Generic numbers rarely transfer.

Go deeper

Adapta's documentation covers the server side in more depth, and docket's covers the client side:

  • Learning the system explains GGUF, quantization and why a loaded model takes requests one at a time, with everyday analogies.
  • Architecture maps Adapta's containers and follows a chat request from the key check to the answer.
  • Operations runbook gives memory sizes per model and sets up the optional vLLM backend for many adapters.
  • Models, gateways, and coding harnesses shows how docket registers an and lists what that compatibility does not cover.

Key takeaways

  • Serving a model is infrastructure work: memory, concurrency, authentication and cost, with the KV cache as the central resource.
  • Quantization trades some quality for memory and speed; evaluate under conditions close to serving.
  • The OpenAI-compatible API makes switching providers easy, but does not guarantee the same features.
  • Adapters let one base model carry many behaviors; the cost depends on whether the engine shares the base across adapters.
  • Output length and model size dominate latency; routing and caching are the main levers.

Check yourself

Why do long contexts and many simultaneous users compete on the same server?

Both make the KV cache grow: it holds notes for every token of context, for every request in progress. They draw on the same memory.

Why does Adapta's llama.cpp backend scale poorly with many fine-tuned endpoints?

Each combination of base and adapter is a full copy in memory, and only a few fit at once. vLLM lets several adapters share one loaded base.

What does "OpenAI-compatible" mean for a server, and what does it not guarantee?

It accepts the same chat completions request and response format, so a client can switch servers by changing the address and the model name. It does not guarantee the same support for tools, structured output, streaming or usage reporting.