Weights are the easy part. The context cache is what actually decides whether the model fits.
In short
A 7B model takes about 14 GB of FP16 weights, but 8K context at batch 8 needs about 30 GB of KV cache. Size the cache first, then precision, then the card.
Why OOM is almost never about the weights
The first time a 13B model hits torch.OutOfMemoryError halfway through loading, the instinct is to rent a bigger card. That instinct is usually wrong, and it is the expensive answer.
Weights load once and then never change. Everything else on the card is allocated per request and scales with how many people you serve at once. A 13B model in 4-bit leaves roughly 19 GB free on a 24 GB card — and a busy endpoint can eat all of it with context alone, while the weights sit there unchanged.
The four consumers of VRAM
Component
7B FP16
7B 4-bit
Scales with
Weights
~14 GB
~4 GB
parameters × precision
KV cache
~0.5 MB / token
~0.5 MB / token
layers × kv-heads × context × batch
Activations
1–3 GB
1–3 GB
batch × sequence length
CUDA context
1–2 GB
1–2 GB
fixed per process
The KV cache formula, written out
For each token you keep two tensors (a key and a value) for every layer and every key/value head. So the cost per token is:
For a 32-layer model with 32 KV heads of dimension 128 in FP16 that is 2 × 32 × 32 × 128 × 2 = 524,288 bytes, or 0.5 MB per token. At 8K context that is 4 GB for a single request. At batch 8 it is 32 GB — and now the card is gone before the weights are even counted.
This is why grouped-query attention changed the economics. A 70B model with 80 layers but only 8 KV heads costs 2 × 80 × 8 × 128 × 2 = 320 KB per token — less per token than the 7B example above, despite having twenty times the parameters. Parameter count tells you the weight size. It tells you nothing useful about the cache.
Recompute it for the exact model you are deploying
import json
cfg = json.load(open("config.json"))
layers = cfg["num_hidden_layers"]
hidden = cfg["hidden_size"]
heads = cfg["num_attention_heads"]
# GQA models expose fewer KV heads than attention heads.
# Falling back to `heads` overestimates KV cache by the
# group factor -- common mistake when sizing Llama-3.
kv_heads = cfg.get("num_key_value_heads", heads)
head_dim = cfg.get("head_dim", hidden // heads)
def kv_bytes_per_token(dtype_bytes=2):
return 2 * layers * kv_heads * head_dim * dtype_bytes
def total_gb(params_b, ctx, batch=1,
weight_bpp=2, kv_bytes=2, overhead_gb=1.5):
weights = params_b * 1e9 * weight_bpp / 1024**3
kv = (kv_bytes_per_token(kv_bytes) * ctx * batch) / 1024**3
# activations + fragmentation, roughly 15% of weights
acts = weights * 0.15
return weights + kv + acts + overhead_gb
print(f"KV/token: {kv_bytes_per_token()/1024:.0f} KB")
for ctx in (4096, 8192, 32768):
for b in (1, 8):
print(f"ctx={ctx:6} batch={b}: "
f"{total_gb(7, ctx, b):5.1f} GB")
Order of operations when the card is too small
Quantize the weights first — 4-bit cuts the largest fixed cost with almost no engineering effort.
Quantize the KV cache too. Many people stop after the weights and find the freed memory immediately absorbed by the cache.
Cut context length before cutting concurrency. Long context is usually a feature request, not a hard requirement.
Reduce the model. A smaller model improves concurrency and latency at the same time — it is a structural win, not a trick.
Only then rent a bigger card, and only if precision or context length is a hard constraint rather than a nice-to-have.
Which card actually fits your case
Pick the row closest to your deployment. All figures are recomputed from the formula above, not from vendor spec sheets.
No reason to pay for a datacenter card at this stage
Batch 4 at 8K context already needs ~16 GB of cache. If you plan to load-test, size for the batch you intend to serve, not for the batch you test with.
7B serving real traffic
48 GB card (L40S / A6000), or 2 × 24 GB
8K context at batch 8 is ~32 GB of cache alone
Headroom matters: a single long request should not stall the queue
Two replicas on separate cards beat one large card for availability
Multi-GPU tensor parallelism splits the weights, not the cache linearly — cross-card traffic becomes the bottleneck before memory does.
70B quantized, low concurrency
2 × 48 GB or 1 × 80 GB (A100 / H100)
4-bit weights ~35 GB — already past a single 24 GB card
GQA keeps cache small: ~0.3 MB per token
80 GB fits weights plus 8K context at modest batch
70B at 4-bit degrades long-tail knowledge first. Keep an FP16 baseline to compare against, or you will misread a quality regression as a prompt problem.
Embedding / reranker only
16–24 GB, no datacenter card needed
Embedding models are small and have no KV cache growth from long generation
Throughput comes from batching, not from VRAM size
A CPU-only host handles low-volume indexing
Do not co-locate the embedding model with a large generation model and assume they share memory politely. They do not — reserve separate headroom.
Common questions
Can I just check the model's parameter count and multiply?
For weights, yes: parameters × bytes per parameter. For anything else, no. Context length and batch size change the answer by an order of magnitude, which is why a model that fits in a quick test can OOM in production with identical code.
Why does the same model OOM in production but not in testing?
Concurrency. Testing usually runs one request at a time; production holds several KV caches at once. Paged attention and continuous batching reduce fragmentation but do not remove the per-token cost.
Is 4-bit quantization lossless enough for production?
It is a trade, not a free lunch. 4-bit weights cost long-tail factual recall first, which is exactly what evaluation sets tend to miss. Keep an FP16 baseline and compare on your own task before shipping.
Where do these architecture numbers come from?
From the model's own config.json — layer count, attention heads, and key/value heads. Do not copy numbers between model families; GQA and MHA differ by a large factor in cache cost.
Do this before renting anything
Step 1. Read config.json and note layers, attention heads, and KV heads.
Step 2. Compute KV bytes per token with the formula above.
Step 3. Multiply by the context length and the concurrency you actually expect — not the concurrency you hope for.
Step 4. Add weights at the precision you can afford to run.
Step 5. Pick the smallest card that leaves 20% headroom, and test at the batch size you sized for.