Inference-Aware Load Balancing for LLMs (part 1): from connections to tokens, what changes when the backend is an LLM

Why connection-based load balancing breaks for language models: tokens, streamed answers, prefill and decode, TTFT and TPOT, a reasoning model thinking before it answers, batching and Little's law, all measured on a real 24B model behind an F5 lab.
Hand-drawn: two requests on one connection each, one finishing in 0.21 s and one streaming for 15.6 s

On this page

Two requests reach the same load balancer, for the same model, on the same server. Each is one TCP connection and one HTTP POST. The first asks a language model to say OK and is finished in 0.21 seconds. The second asks for a detailed paragraph about BGP and streams back for 15.6 seconds. Nothing in either request told the load balancer which one it would be. This is part 1 of a new series, Inference-Aware Load Balancing for LLMs, which builds, on a real data-centre fabric with an F5 BIG-IP at the edge, the layer that can see tokens, models and caches. This post goes with a video on the LevelUP I.T. YouTube channel.

Part 1 is the foundation: what a load balancer knows about a request, what a token is, how a chat completion streams back, the two phases of every request (prefill and decode), the three numbers that describe it (TTFT, TPOT and end-to-end), what batching does to them, and Little's law. Every number in this post was measured on the lab, against one real model, LFM2.5-24B-A2B, running on vLLM on a CPU node.

The series: the BIG-IP at the edge, NGINX Gateway Fabric, the llm-d endpoint picker, and the vLLM model servers
The series: the BIG-IP at the edge, NGINX Gateway Fabric, the llm-d endpoint picker, and the vLLM model servers

What you'll see

  • What a classic load balancer knows about a request, and why connection counting worked for web traffic.
  • Tokens, measured on the model's own tokenizer, and how a model writes one token at a time.
  • The OpenAI chat completions API: what crosses the wire, and what it never says.
  • The lab for this series, and one real 24-billion-parameter model deployed on a CPU node, configuration first.
  • A streamed answer event by event, a 48-second first request after start, and a reasoning model thinking before it answers.
  • Prefill and decode, TTFT, TPOT and end-to-end time, with a long and a short prompt measured.
  • One, four and eight streams at once, and Little's law checking the client.
  • What a load balancer can and cannot know, and what a BIG-IP in front of model servers can already do today.

What a load balancer knows about a request

A BIG-IP sees the client's and the server's addresses and ports, and the state of every TCP connection; it counts the open connections on each pool member. With an HTTP profile it reads the method, path, host and headers; a health monitor tells it whether a member is up; a cookie or a source address keeps a client on one member. The load-balancing methods measured in part 5 of the F5 series use exactly that: round robin sends each new connection to the next member, least connections to the member with the fewest open connections, and the ratio, fastest, observed and predictive methods weight members by ratios, connection counts or response times averaged over the last few seconds.

All of them rest on one assumption: that a connection is roughly one unit of work, and that it ends soon. For web traffic it holds. A page, an image or an API call is small, the server has most of the answer when it starts sending, and requests differ over a narrow range. So the number of open connections tracks the work in progress on each server, and a recent average response time is a fair forecast of the next one.

What a classic load balancer sees, and the assumption behind every method
What a classic load balancer sees, and the assumption behind every method

Now the two requests from the opening, measured on this lab against the same model, one at a time:

RequestPrompt tokensAnswer tokensTime to first tokenEnd to end
"Say OK." (max 4 tokens)1340.110 s0.212 s
"Write a detailed paragraph about BGP." (fixed at 512 tokens)185120.772 s15.621 s

A 74-times difference, one connection each, and nothing in the headers to tell them apart. The second answer streams for the whole 15.6 seconds, so its connection stays open for the whole time, and while it is being written it shares the server's compute with every other request on that server.

Same model, same server: 0.21 s against 15.6 s
Same model, same server: 0.21 s against 15.6 s

Tokens

A language model does not read letters or words. A tokenizer splits the text into pieces from a fixed vocabulary (128,000 entries for this model), and the model works on the numbers (ids) of those pieces. When it writes, it produces one new token id at a time. Every price, limit and speed in LLM serving is therefore counted in tokens.

The series' client (llm.py, one Python file, standard library only, in the lab repository) shows the split on the model itself: it calls vLLM's /tokenize endpoint for the ids, then /detokenize on each id to show its piece of text.

llm tokens http://lfm-24b-a2b:8000 lfm2.5-24b-a2b 'The BIG-IP load-balances connections, not tokens.'
6 words -> 14 tokens
[The]  [ B]  [IG]  [-]  [IP]  [ load]  [-b]  [al]  [ances]  [ connections]  [,]  [ not]  [ tokens]  [.]
ids: [597, 378, 5320, 21, 5563, 7524, 1484, 285, 2452, 13212, 20, 513, 40043, 22]
Six words, fourteen tokens: how this model's tokenizer splits a sentence
Six words, fourteen tokens: how this model's tokenizer splits a sentence
TextWordsTokens
The BIG-IP load-balances connections, not tokens.614
A load balancer distributes incoming network traffic across multiple servers.1013
10.20.30.161 llm.f5lab.test213

Plain English is a little over one token per word; addresses, names, numbers and configuration are much denser. The length of a request in bytes or words is only a rough guide to its length in tokens: a gateway that wants the real number must run the model's tokenizer.

How a model writes one token

Each token so far becomes a vector (2,048 numbers for this model). The vectors pass through the model's 40 layers; in the attention layers each position looks back at every earlier position. At the end, the last position's vector becomes a score for each of the 128,000 vocabulary entries, one entry is chosen, and that is the next token. It is appended, and the whole step runs again. This is autoregressive generation: every token depends on all the tokens before it, so an answer is written one step after another, never in parallel, and takes time in proportion to its length.

Choosing the entry is sampling. The scores become probabilities; with a temperature above zero the pick is random, weighted by those probabilities, and a higher temperature gives more varied text. With temperature 0 the most likely token is always chosen, so the same prompt gives the same answer. Every measurement in this series uses temperature 0, so a change in speed is the system, not the text. The client's --fixed option also sets min_tokens equal to max_tokens and ignore_eos, so an answer is exactly N tokens long.

One step of generation: vectors, layers, a score per vocabulary entry, one token
One step of generation: vectors, layers, a score per vocabulary entry, one token

The chat completions API

Almost every model server speaks the API OpenAI defined: POST /v1/chat/completions with a JSON body.

{
  "model": "lfm2.5-24b-a2b",
  "messages": [{"role": "user", "content": "In two sentences, what does a load balancer do?"}],
  "max_tokens": 220,
  "temperature": 0,
  "stream": true,
  "stream_options": {"include_usage": true}
}

A gateway could read the model (so the pool that can answer: part 6 routes on it), the messages (so the prompt's length in tokens, and the prefill cost), max_tokens (only a ceiling), and whether the answer streams. What the request never says is how many tokens the answer will have. The model decides that while it writes: it stops at its end-of-sequence token or at max_tokens.

What crosses the wire: the request body, and the answer as Server-Sent Events
What crosses the wire: the request body, and the answer as Server-Sent Events

This post is for paying subscribers only

Subscribe to LevelUp I.T. newsletter and stay updated.

Don't miss anything. Get all the latest posts delivered straight to your inbox. It's free!
Great! Check your inbox and click the link to confirm your subscription.
Error! Please enter a valid email address!