GPU Lab / interactive book
GPU mapSource ↗

CHAPTER 12

What limits an inference step?

How do a growing cache, the invocation phase and batch size change an inference budget?

Start with the operation

PyTorch

import torch
import torch.nn.functional as F

# Three cached tokens, each with four components.
k_cache = torch.arange(12, dtype=torch.float32, device="cuda").reshape(3, 4) / 10
v_cache = torch.arange(12, dtype=torch.float32, device="cuda").reshape(3, 4)
q_new = torch.tensor([[1., 0., -1., 0.]], dtype=torch.float32, device="cuda")
k_new = torch.tensor([[0.2, 0.3, 0.4, 0.5]], dtype=torch.float32, device="cuda")
v_new = torch.tensor([[12., 13., 14., 15.]], dtype=torch.float32, device="cuda")
k_cache = torch.cat((k_cache, k_new), dim=-2)
v_cache = torch.cat((v_cache, v_new), dim=-2)
out = F.scaled_dot_product_attention(
    q_new, k_cache, v_cache, dropout_p=0.0, is_causal=False)
print(k_cache.shape, out)
Desktop setup

A decoder reuses keys and values from earlier tokens. This complete CUDA-device PyTorch example starts with three cached tokens, appends one new key and value, and computes one new query's weighted result over all four tokens. Each row is one token and each column is one component, as in the previous chapter. reshape(3, 4) arranges twelve numbers into three rows of four. dim=-2 selects the first dimension of these two-dimensional tensors, so concatenation appends rows.

F is the short name given to PyTorch’s functional operations by the import. Its scaled_dot_product_attention function performs the score, softmax and weighted-value calculation from the previous chapter. dropout_p=0.0 keeps every attention weight. is_causal=False is intentional: the sole query is the newest token, so all four supplied keys are permitted. For a multi-query prompt, a causal mask is needed to hide future tokens. This example receives Q/K/V directly; it contains no model weights, projections or token generator.

torch.cat creates new tensors and copies existing entries. Production caches can instead reserve storage and write new positions. The small code illustrates cache contents, not efficient cache management. PyTorch handles allocation and implementation selection. The browser budgets below use a separate, explicitly synthetic decoder; they do not estimate this four-component PyTorch example.

Run the examples below in your browser with no installation or GPU required. The browser simulates the calculation; it does not execute PyTorch or GPU code. To run the PyTorch snippet itself, use a Python environment with PyTorch and an NVIDIA GPU.

Idea 1 of 3

Reserve space for past tokens

Definitions

KV cache
Stored keys and values from earlier tokens, reused by later attention queries.
Decode
Processing one new token per sequence while reusing earlier cache entries.
Batch
Independent sequences processed together.
Invocation
One execution of the work being counted: a whole prompt or one decode step for each sequence.

Start with the cache just before one decode invocation. First count storage, without asking about time. The cache payload is 2 × layers × batch × cached tokens × KV width × bytes/value. The first factor of two stores both keys and values.

The synthetic decoder stores keys and values for 32 layers, with 1,024 components in each key or value and two bytes per component. A layer is one stage of the model. These are teaching dimensions; the opening code uses one set of four-component values.

Here batch stays at one. The selected token count S is the cache length before a decode invocation; the invocation appends one entry per sequence. Payload capacity excludes any reserved unused slots and allocator metadata.

Try it

Predict the cache payload when the cached length changes from 512 to 2,048 tokens. Run at 512, change only the length, and explain the factor. Distinguish the existing payload from the new bytes appended.

Predict the result, then press Run.

Idea 2 of 3

Compare two invocation phases

Definitions

Prefill
Processing the prompt tokens and building their cache entries.
Lower bound
An ideal minimum implied by the counted work and assumed rate; it is not a prediction of measured elapsed time.
FLOP
One floating-point arithmetic operation in the work estimate.

Now keep batch = 1 and S = 512. For prefill, S means the number of prompt tokens processed from an empty cache. For decode, S means the number already cached, followed by one new token. These invocations do different amounts of work.

This synthetic budget assumes 7 billion learned weight values (parameters), 32 layers, hidden width 4,096, KV width 1,024 and two bytes per stored value. Hidden width sizes the model’s internal vectors in the operation estimate; KV width is the number of cached key or value components per token per layer. It is not the shape of a named model. One invocation reads the 14 GB weight payload once, with ideal reuse across its batch. Activations, communication, intermediate tensors, allocation, cache-copy costs and other overheads are excluded.

Decode reads the S-token cache and writes one new entry per sequence. Prefill writes the full S-token cache; this model counts no previous-cache read. The prefill operation estimate uses full square attention, without causal triangular savings. Dense-weight work is approximately 2 × parameters × tokens; attention adds 4 × layers × batch × S × hidden width for decode, and another factor S for prefill.

The selected ideal rates are 1,000 decimal GB/s (1012 bytes/s) and 100 TFLOP/s (1014 floating-point operations/s). These are teaching inputs, not measured specifications. Divide counted bytes by bandwidth and counted operations by compute rate; take the larger time assuming ideal overlap. Real elapsed time needs a measurement with a matching scope.

Try it

Predict which bound is larger for decode, then run. Change only phase to prefill. Explain why the compute work changes even though the model reads the same weight payload per invocation.

Predict the result, then press Run.

The larger lower bound identifies the tighter of these two ideal budgets. It does not diagnose an actual GPU bottleneck. This page supplies no timing measurements; uncounted work and implementation details need separate investigation.

Idea 3 of 3

Share a weight read across a batch

Return to decode with S = 512 cached tokens per sequence. Increasing batch produces more tokens per invocation. The ideal budget reuses each weight read across the whole batch, while each sequence needs its own cache.

This synthetic budget assumes 7 billion learned weight values (parameters), 32 layers, hidden width 4,096, KV width 1,024 and two bytes per stored value. Hidden width sizes the model’s internal vectors in the operation estimate; KV width is the number of cached key or value components per token per layer. It is not the shape of a named model. One invocation reads the 14 GB weight payload once, with ideal reuse across its batch. Activations, communication, intermediate tensors, allocation, cache-copy costs and other overheads are excluded.

The selected ideal rates are 1,000 decimal GB/s (1012 bytes/s) and 100 TFLOP/s (1014 floating-point operations/s). These are teaching inputs, not measured specifications. Divide counted bytes by bandwidth and counted operations by compute rate; take the larger time assuming ideal overlap. Real elapsed time needs a measurement with a matching scope.

Compare both milliseconds per invocation and the bound divided by the number of new tokens. The latter is a budget per produced token, not a user's time to receive the next token. More batch members also need more cache capacity.

Try it

Predict which grows with batch: weight payload read, KV cache, or new tokens. Run at batch 1, change only batch to 4, and compare the invocation bound with the bound per new token. Explain the ideal reuse assumption.

Predict the result, then press Run.

Optional: plan a real timing comparison

Use a real model and document its shapes, datatype, batch, context, attention implementation and cache allocation strategy. Choose whether timing covers one invocation or the whole request. Warm up, account for asynchronous CUDA execution, synchronise the chosen interval, and repeat measurements. A result below this page's bound would first require checking units, counted work, assumed rates and timing scope. The browser exercises run no PyTorch or GPU benchmark.

Try a new case

Apply it

Compare a cache for batch 4 with 128 tokens per sequence against batch 1 with 512 tokens per sequence, keeping layers, width and value size fixed. Which needs more cache payload? Does equal cache payload imply equal per-token work or measured latency?

Compare your answer
Both cache payloads are 67,108,864 bytes: batch × sequence length is 512 in each (67,108,864 bytes for the second case). Decode produces 4 new tokens in the first invocation and 1 in the second. Weight reuse and per-token accounting differ. Equal cache capacity alone does not establish equal work per token or measured latency.

References

Use these official references to check the concepts and code on this page.

These browser exercises do not run an installed PyTorch or CUDA toolkit. For optional local execution, choose documentation matching your installed tools. Links open online in a new tab; the questions and answer comparisons below work offline.

Use the documentation

Use the references to explain each answer, then apply it to this page’s example. No installation is needed.

Install GPU Lab

Give GPU Lab its own icon and window. All lessons and simulators work offline once the course is saved.

Use your browser’s install option:

If your browser has no install option, try Chrome or Edge.

App status

Version

Checking for updates

Offline access

Not saved for offline use

Save all lessons, browser simulators, diagrams and example files. Desktop execution needs separate PyTorch or CUDA/GPGPU-Sim tools; their installations are not part of this offline download. External links need internet access.