Skip to content

What an Inference Engine Actually Does

7 min readbuilds on Continuous Batching

A request through the engine

After the first token, repeat 4 → 5 → 4 until stopping. The highlight introduces the stages; its timing does not model runtime duration.

A model's forward pass maps tokens to logits. Serving it adds a different problem: many requests arrive at different times, use different amounts of memory, and expect a response while other requests are still running.

An inference engine coordinates that work. It loads weights, schedules model execution, manages cached attention state, and returns generated tokens. A server adds the surrounding API and operational controls. Some projects package both together.

Follow one request

A request first passes through validation and tokenization. Chat messages need the checkpoint's chat template; raw text and a list of message objects are not interchangeable model inputs. The tokenizer must match the weights.

The request then waits for admission. The scheduler decides whether it can run within the current sequence, token, and memory budgets. A full cache does not imply one universal response: an engine may queue work, preempt a request for later recomputation, offload state, or reject a request according to its configuration.

Request data flow and supporting state

  1. HTTP request → formatting and tokenization → request queue

    The server validates inputs and creates token IDs and generation settings.

  2. Scheduler → batch construction → model kernels

    The scheduler selects prefill chunks and decode work. The cache manager allocates blocks and supplies their tables; the model loader supplies weights.

  3. Logits → constraints and sampling → output token

    The token goes to detokenization and the response stream. If generation continues, its ID returns to the scheduler for the next model pass.

GPU state

Weights, KV cache, and workspaces. Model kernels read weights and cache entries and append newly computed keys and values.

Request completion

Stop, cancellation, or failure ends scheduling for that request. Release its cache references; reusable prefix blocks may remain cached.

The serving responsibilities and their connections. The exact process boundaries and implementations differ between engines.

Prefill processes the prompt through the model and creates its KV entries. Prompt positions can be computed in parallel within a layer under the attention mask; the layers still execute in dependency order. The final prompt position supplies logits for the first generated token.

The sampler selects that token. For a streaming request, the server can detokenize and emit it immediately. On the next model pass, the selected token becomes input, its K and V are appended, and its logits produce the next selection. A token's KV entry is created when the model processes it, not merely when the sampler chooses it.

This repeats until a stop condition, length limit, cancellation, or error. Request-owned blocks can be released; reusable prefix blocks may remain cached under the engine's eviction policy. Streaming and detokenization happen throughout generation, rather than once after the complete decode loop.

Computed toy schedule: exclusive prefill, batched decode

Assume 8 ms per prompt token and 80 ms per decode iteration. Ready prefills run before the next decode iteration. Arrivals during an iteration wait until it finishes.

RequestArrivesPrefillFirst tokenTTFTLast tokenEmitted
A0 ms0–112112 ms112 ms912 ms8/8
B150 ms192–368368 ms218 ms1312 ms12/12
C420 ms448–512512 ms92 ms912 ms6/6

The first token is sampled at prefill completion. Later prefills interrupt ongoing decoding in this toy policy, making some token gaps longer than 80 ms. Sampling and transport overhead are omitted.

A constructed three-request schedule shows queueing, prefill, and streamed output. Timing constants illustrate the mechanism rather than any model's speed.

What the scheduler coordinates

With a static batch, membership stays fixed until its work finishes. A short request can leave an empty lane while another continues. Continuous batching allows completed requests to leave and new work to enter between iterations, subject to the scheduler's budgets.

Constructed schedule: two decode slots, six queued requests

Request lengths A–F: 2, 6, 3, 5, 2, 4 steps. Every step is assumed to take the same time; prefill is omitted.

Step123456789101112131415
Slot 1AA————CCC——EE——
Slot 2BBBBBBDDDDDFFFF

22 occupied slots / 30 available slots = 73.3% slot occupancy. Completion: 15 steps.

Slot occupancy is not measured GPU utilization. Actual step time changes with batch size and context length.

Compare how many decode slots remain occupied as each request finishes in this toy schedule.

A high slot-occupancy percentage is useful, but it is not the same as tensor-core utilization or memory-bandwidth utilization. A busy GPU can still spend much of its time moving data. Actual throughput depends on the kernels, batch sizes, sequence lengths, and communication as well as admission.

A long prefill can also delay ongoing decode work. Chunked prefill and continuous batching let an engine distribute prompt work across iterations. Priority policies choose which work gets those iterations. These are separate decisions, and their effects need workload-specific measurement.

The cache is part of the capacity plan

For a uniform full-attention decoder, cache bytes per stored token across the whole model are

2 L HKV dhead b.2\,L\,H_{KV}\,d_{head}\,b.

The factor two counts keys and values. Use the number of KV heads, not query heads. The formula assumes equal dimensions in every layer; hybrid attention and compressed-cache architectures need their own accounting.

For 80 layers, eight KV heads, dimension 128, and BF16 cache elements, that is 327,680 bytes per token, or 320 KiB. A 32,768-token request needs about 10.74 decimal GB of KV data before page rounding and metadata.

KV cache per request · BF16/FP16 · decimal GB
8B
4.29 GB
70B
10.74 GB
405B
16.91 GB
0.010.11101001k2k4k8k16k32k64k128kTokens (k = 1,024; log scale)KV cache (GB, log)

Dimensions from Llama 3, Table 3: 32/80/126 layers, 8 KV heads, head dimension 128. Cache bytes = 2 × layers × 8 × 128 × context × 2. This is aggregate cache storage before sharding; weights and runtime memory are additional.

Change the retained context length to compare the three models’ KV storage.

Weights, runtime workspace, graph allocations, and safety headroom occupy memory too. Adding GPU capacities gives an optimistic aggregate bound only when sharding and placement make that memory usable. Replicated KV heads or weights can reduce it. The memory-management post works through this budget.

Where engines make different choices

What choices live at each stage
select a stage
KV cache
Store, reuse, and evict per-request attention state.
Layout
Paged storage allocates fixed-size blocks on demand. Final blocks can still contain unused slots.
Sharing
Prefix caching / RadixAttention (SGLang): shared prompt prefixes reuse blocks across requests.
Precision
KV precision is separate from weight precision. Supported low-precision cache formats, scales, and quality effects depend on the backend.
Compare supported choices against your model and workload.

Select a stage to inspect the decisions it contains.

Kernels determine how matrix operations and attention use the device. Paged KV allocation determines how growing sequences occupy memory. Quantization changes storage formats and arithmetic paths. Parallelism distributes work and adds communication.

Execution
Eager operations, compilation, CUDA graphs, and specialized kernels can coexist in one runtime.
Memory
KV layout, prefix sharing, quantized weights, and device placement must fit the workload.
Scheduling
Admission, token budgets, chunked prefill, and priorities determine which work runs next.
Deployment
Model support, hardware support, maintenance, and APIs constrain the shortlist.
An engine can combine eager and compiled execution paths.

Offline generation and online serving

The vLLM offline API takes a finite collection of prompts and returns generated results. It can schedule that batch internally; this example does not stream results or listen for new network requests.

from vllm import LLM, SamplingParams
 
# Requires a compatible installation, suitable hardware, and model access.
llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct")
conversations = [
    [{"role": "user", "content": "Explain why the sky looks blue."}],
    [{"role": "user", "content": "Name three common sorting algorithms."}],
]
results = llm.chat(conversations, SamplingParams(temperature=0, max_tokens=80))
for result in results:
    print(result.outputs[0].text)

For an online service, start the server instead, in a separate process with enough resources for its model:

The offline API and online-serving guide document these entry points.

Next: kernel fusion, where avoiding an intermediate memory round trip can matter as much as reducing arithmetic.

Additional reading