Skip to content

Memory Management: Fitting a Model and Deciding Concurrency

GPU memory budget · assumed 80 GB

Planned KV allocation filled

0 GB80 GB
Weights
42.0 GB
Runtime
6.0 GB
KV cache
24.0 GB
Unallocated
8.0 GB

At the filled state: 42 GB weights + 6 GB runtime + 24 GB KV, leaving 8 GB headroom. Animation time is not loading or generation time. The bar uses one 0–80 decimal GB scale.

A model fitting on a GPU is only the first memory check. Serving also needs temporary buffers and a KV cache for every active request. A configuration that loads successfully can still run out of memory when longer prompts arrive or a larger batch starts decoding.

The useful calculation is a budget: subtract everything the runtime already needs, then translate the remaining bytes into stored token positions. This gives a capacity bound to test, rather than a promised concurrency level.

What occupies memory

Weights depend on parameter count and representation. A rounded 70-billion-parameter model has about 140 decimal GB of raw 16-bit weights, 70 GB at eight bits, or 35 GB at four bits. Quantization scales, zero points, mixed-precision tensors, alignment, and runtime copies add overhead.

Runtime memory includes activations, attention workspaces, communication buffers, graph captures, and allocator reservations. Its size can change with prefill chunk size, batch shape, and backend. KV cache is not the only allocation that grows with workload.

KV cache stores keys and values for processed token positions. Paged allocation reduces unused reservations, but occupied entries still cost bytes. Finally, leave headroom for allocations that the simplified calculation misses.

Assumed 80 GB budget: 42 GB weights, 6 GB workspace
GB
weights · 42 GBCUDA + activations · 6 GBKV cache · 24 GBheadroom · 8 GB
Weights are usually resident. Reserve peak workspace and headroom before assigning the remainder to KV blocks. Workspace can change with the workload.

An assumed 80 GB budget, not a measurement of a named model: 42 GB weights, 6 GB runtime workspace, 24 GB KV cache, and 8 GB headroom.

All GB figures here use 10910^9 bytes. MiB and GiB instead use powers of two; mixing the units can turn apparent spare capacity into an out-of-memory error.

Bytes per stored token

For ordinary full-context attention with the same KV shape at every layer,

btoken=2LHKVdhs,b_{\text{token}}=2L H_{\text{KV}} d_h s,

where LL is layer count, HKVH_{\text{KV}} is the number of KV heads, dhd_h is head dimension, and ss is bytes per stored value. The leading two counts keys and values.

For an 80-layer architecture with eight KV heads, dimension 128, and 16-bit storage:

btoken=2×80×8×128×2=327,680 bytes=320 KiB.b_{\text{token}}=2\times80\times8\times128\times2 =327{,}680\text{ bytes}=320\text{ KiB}.

These are the Llama 3.1 70B KV dimensions. With full cache storage, 4,096 tokens cost about 1.34 GB for one sequence before block padding and metadata. Quantized KV, sliding windows, hybrid layers, latent attention, and distributed placement need their own accounting.

KV cache growth

Hypothetical head layouts: 80 layers, 64 query heads, 8 KV heads, head dimension 128. K and V use two bytes per element.

Six cells stand for each token row; they are not individual heads or vector dimensions. The first three tokens are prefilled. Each step adds one K row and one V row at every layer.

3 tokens · 960.0 KiB per request across all layers
Bar maximum: 20.0 MiB for eight tokens with MHA.
2 × 80 × 8 × 128 × 3 × 2 = 983,040 bytes
The cached keys and values grow with processed positions. The newest sampled token joins the cache when the next model pass processes it.

For requests with lengths T1,…,TCT_1,\ldots,T_C, the unshared payload is btoken∑iTib_{\text{token}}\sum_i T_i. The common shortcut C×T×btokenC\times T\times b_{\text{token}} assumes every request has the same stored length. Shared prefixes reduce duplicated entries; paging rounds each private allocation to block boundaries.

From bytes to a concurrency bound

If 14 GB remains for KV cache after weights, runtime memory, and your chosen reserve, the ideal capacity is

⌊14×109327,680⌋=42,724\left\lfloor\frac{14\times10^9}{327{,}680}\right\rfloor=42{,}724

stored token positions. Twenty requests at 2,000 positions use 40,000 of them, leaving only about 6.4% of that KV budget free. Eighteen such requests use 36,000 positions and leave about 15.7%. These percentages concern the KV allocation, not total GPU memory.

Token slots: 42,724 (14 GB KV budget)
uniform lengths, no sharing
context / request
2,700
×
×
max concurrent
15
040,500 / 42,724 slots used
25616,384

An idealized 14 GB KV budget at 327,680 bytes per token. Uniform per-request lengths trade against concurrency. Block padding, sharing, and future growth are excluded.

A scheduler also needs room for requests to grow. Admitting everything that fits now may force preemption later. Conversely, reserving every request's worst-case completion wastes capacity. Real admission policies balance that risk against the workload's length distribution.

Idealized budget · decimal GB
160 GB total
weights · 140 GBCUDA+act · 6.00 GBKV budget · 14.0 GBfree · 0.00 GB
KV per token
320.0 KiB
Total KV token slots
42,724
Max concurrent
1
Ctx / request
32k

Assumptions: ideal weight/KV sharding, raw weight payload, and 3 GB workspace per GPU. No quantization metadata, page rounding, replication, or safety reserve is included. Smaller presets are hypothetical GQA architectures. Context options are memory calculations, not supported model limits. Profile a real deployment before admission decisions.

Selected dimensions: 80 layers × 8 KV heads × 128 head dimension; 2 bytes per cache value.

model
precision
gpu
num gpus
context
kv precision

Explore an idealized budget. Raw weight payload and KV entries are evenly sharded, with an assumed 3 GB runtime allocation per GPU. Quantization metadata, replicated tensors, block padding, and safety headroom are additional costs.

The calculator is not a compatibility checker. A model's supported context length and a backend's quantization formats are separate constraints. Tensor parallelism may replicate KV heads when the parallel degree exceeds the number of heads; total cluster memory is not always perfectly divisible among GPUs.

Long context changes the budget

At 131,072 positions, the 70B example needs about 42.95 GB of FP16 KV payload for one sequence. That is separate from its weights. A smaller model may have a smaller cache, but parameter count alone does not determine it.

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.

Compare full-context FP16 KV payloads using the Llama 3 paper’s architecture dimensions.

Using the paper’s 405B dimensions—126 layers, eight KV heads, and dimension 128—the same context requires about 67.65 GB. Use the checkpoint’s actual KV configuration when sizing a deployment.

What offloading costs

Moving tensors to host RAM or storage expands the available capacity, but introduces a different bottleneck. If a design transfers DD bytes for each step over a link sustaining BB bytes per second, transfer alone takes at least D/BD/B seconds. Transfers can overlap computation, but cannot escape the bandwidth requirement.

Transfer-time lower bound: bytes ÷ bandwidth

Assumed 32 GB/s link
250 ms
Assumed 7 GB/s storage path
1143 ms

Ideal transfer time only: no compute, latency, contention, or overlap. CPU-executed layers are a different case; their weights need not cross the GPU link every step. An SSD-backed file may already be cached in RAM.

Transfer-time lower bounds at the stated assumed bandwidths. Change the tensor size to see how many seconds the transfer alone requires.

CPU execution is not the same as copying all weights to the GPU every token. A runtime can execute some layers on the CPU and transfer their activations. Likewise, memory-mapping a model file does not mean every step reads it from an SSD: the operating system may keep pages in RAM. Identify which tensors move, how often, and where computation runs before estimating speed.

Fast GPU interconnects help distributed execution, but their headline bandwidth is not directly comparable with HBM bandwidth without checking direction, topology, and the collective being used.

A capacity calculation to adapt

from math import floor
 
layers, kv_heads, head_dim, bytes_per_value = 80, 8, 128, 2
bytes_per_token = 2 * layers * kv_heads * head_dim * bytes_per_value
kv_budget_bytes = 14 * 10**9  # after other allocations and reserve
slots = floor(kv_budget_bytes / bytes_per_token)
assert bytes_per_token == 327_680
assert slots == 42_724
 
stored_lengths = [2000] * 18
payload = sum(stored_lengths) * bytes_per_token
print(f"KV payload: {payload / 10**9:.3f} GB")
print(f"Remaining KV budget: {(kv_budget_bytes - payload) / 10**9:.3f} GB")

To reduce memory, choose a compatible lower-precision representation, shorten retained context, reuse valid prefixes, or distribute the model. GQA is an architectural choice that usually requires a model trained or adapted for it, rather than an engine switch. The next post examines quantization, including its overhead and quality tradeoffs.

Additional reading