Warum ein 35-GB-quantisiertes LLM trotzdem eine 80-GB-GPU überlastet
Beim Deployment von Open-Weight-Modellen stoßen viele Ingenieure an dieselbe Grenze: Der KV-Cache und die Architektur des Modells treiben den tatsächlichen VRAM-Bedarf weit über die simple Quantisierungsrechnung hinaus.

Jeder Ingenieur, der Modelle mit offenen Gewichten deployt, durchläuft dieselbe Initiation:
Man nimmt ein Modell mit 70B Parametern, quantisiert es auf 4 Bit ( 70B * 0.5 Bytes ≈ 35 GB ), stellt eine einzelne 80GB A100 oder H100 bereit (oder zwei 24GB GPUs) und startet vLLM oder llama.cpp mit einem 64K Kontextfenster und einer kleinen gleichzeitigen Batch-Größe.
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.50 GiB. Enter fullscreen mode Exit fullscreen mode Wie kann ein Modell mit "35 GB" auf einer 80 GB GPU aus dem Speicher laufen?
Denn die Modellgewichte sind nur der statische Grundbedarf des LLM-Speichers. Rechnet man den Overhead der Quantisierungs-Skalenblöcke, die Prefill-Aktivierungspuffer, die Reservierungen des CUDA/PyTorch-Workspaces und den stillen Riesen, den Key-Value (KV) Cache, ein, kann sich der tatsächliche VRAM-Bedarf leicht verdoppeln oder verdreifachen.
Hier ist die exakte Byte-Mathematik hinter der VRAM-Allokation von LLMs in der Produktion, wie Grouped-Query Attention (GQA) und DeepSeeks Multi-Head Latent Attention (MLA) die Skalierungskurve verändern, und wie man die GPU-Hardware richtig dimensioniert, bevor man für Cloud-Instanzen bezahlt.
Zur Laufzeit setzt sich der gesamte GPU-Speicherverbrauch aus vier getrennten Allokationen zusammen:
Total VRAM = M_weights + M_kv_cache + M_activations + M_runtime_overhead Enter fullscreen mode Exit fullscreen mode Komponente Skalierungsfaktor Typischer Anteil (kurzer Kontext) Typischer Anteil (128K Kontext / Batch 16) 1. Modellgewichte ( M_weights ) Parameter × Bits_Effective 75% – 85% 30% – 45% 2. KV Cache ( M_kv_cache ) Batch × Context_Len × Layers 5% – 15% 45% – 65% 3.
Aktivierungspuffer ( M_act ) Batch × Prefill_Chunk × Hidden_Dim 3% – 8% 5% – 10% 4. CUDA / Runtime Overhead Fix + PagedAttention Block Tables 1.5 GB – 3.0 GB 2.0 GB – 4.0 GB 2. Warum 4-Bit-Quantisierung nie genau 0.5 Bytes/Parameter entspricht Eine verbreitete Faustregel besagt, dass INT4 = 0.5 Bytes pro Parameter entspricht.
In der Praxis zerstört naive uniforme 4-Bit-Quantisierung die Perplexität des Modells, weil die Gewichtungsverteilungen extreme Ausreißer enthalten.
Moderne Quantisierungsformate ( AWQ, GPTQ, GGUF K-Quants ) gruppieren Gewichte in Blöcke (z.B. 32, 64 oder 128 Gewichte) und speichern FP16-Skalen und Nullpunkte mit höherer Präzision pro Block, während Embedding- und Output-Head-Schichten ( lm_head ) mit 16-Bit- oder 8-Bit-Präzision bleiben:
// Effektive Bits pro Parameter (bpw) in Produktionsformaten const EFFECTIVE_BITS_PER_WEIGHT: Record < string, number > = { FP16: 16.0, // 2.00 bytes/param BF16: 16.0, // 2.00 bytes/param FP8: 8.5, // ~1.06 bytes/param (channel/tensor scaling factors) INT8: 8.5, // ~1.06 bytes/param INT4_AWQ: 4.5, // ~0.5625 bytes/param (group_size=128 FP16 scales) GGUF_Q5_K_M: 5.7, // ~0.71 bytes/param GGUF_Q4_K_M: 4.85 // ~0.606 bytes/param (super-blocks + unquantized tensors) }; export function calculateWeightVramGB ( paramsBillions: number, format: keyof typeof EFFECTIVE_BITS_PER_WEIGHT ): number { const bpw = EFFECTIVE_BITS_PER_WEIGHT [ format ]; return ( paramsBillions * 1 e9 * ( bpw / 8 )) / ( 1024 ** 3 ); } Enter fullscreen mode Exit fullscreen mode Bei einem 70.6B Modell in GGUF Q4_K_M liegt der Speicherbedarf der Gewichte allein bei etwa 39.8 GB (GiB), also bereits 5 GB über der groben Schätzung von 35 GB.
Während der autoregressiven Generierung würde das Neuberechnen der Attention-Keys ($K$) und Values ($V$) für alle vorherigen Tokens bei jedem Schritt ein Decoding von $O(N^2)$ bedeuten. Stattdessen cachen Inferenz-Engines die $K$- und $V$-Tensoren über alle Transformer-Schichten hinweg.
Für ein Modell mit Grouped-Query Attention (GQA) (wie Llama 3/4, Qwen 3 oder Mistral) ergibt sich die KV-Cache-Größe in Bytes wie folgt:
KV_Bytes = 2 (K und V) × n_layers × n_kv_heads × head_dim × seq_len × batch_size × bytes_per_kv_element Enter fullscreen mode Exit fullscreen mode Setzen wir die realen Architekturkonstanten eines 70B GQA-Modells ein ( n_layers = 80, n_kv_heads = 8, head_dim = 128 ):
2 × 80 × 8 × 128 × 2 bytes = 327,680 bytes ≈ 320 KB pro Token Enter fullscreen mode Exit fullscreen mode Skaliert man das auf ein 64K Kontextfenster ( 65,536 Tokens) mit batch_size = 2: 320 KB × 65,536 × 2 = 41,943,040,000 bytes ≈ 39.06 GiB! Enter fullscreen mode Exit fullscreen mode Man beachte, was da gerade passiert ist:
(Und hätte das Modell klassische Multi-Head Attention mit 64 KV-Heads statt 8 GQA-Heads verwendet, hätte derselbe KV Cache 312.5 GiB benötigt!)
Bei DeepSeek-V3 und DeepSeek-R1 (671B MoE-Gesamtparameter, 37B aktiv) würde das Halten standardmäßiger GQA-Tensoren über 61 Schichten bei 128K Kontext die PCIe/NVLink-Bandbreite zum Flaschenhals machen.
Stattdessen projiziert Multi-Head Latent Attention (MLA) Keys und Values gemeinsam in einen komprimierten Low-Rank-Latentvektor ($c_t^{KV}$, typischerweise Dimension $d_c = 512$) plus eine entkoppelte RoPE-Key-Dimension ($d_R = 64$):
export function calculateKvCacheBytesPerToken ( arch: { attentionType: ' GQA ' | ' MLA '; numLayers: number; numKvHeads?: number; headDim?: number; kvLoraRank?: number; // e.g. 512 for DeepSeek V3/R1 qkRopeHeadDim?: number; // e.g. 64 kvBytesPerElem: number; // 2 for FP16, 1 for FP8 KV cache }): number { if ( arch. attentionType === ' MLA ' ) { const compressedDim = ( arch. kvLoraRank?? 512 ) + ( arch. qkRopeHeadDim?? 64 ); // MLA caches only the joint latent vector per layer (no 2x K/V multiplier!) return arch. numLayers * compressedDim * arch. kvBytesPerElem; } return ( 2 * arch. numLayers * ( arch. numKvHeads??
8 ) * ( arch. headDim?? 128 ) * arch. kvBytesPerElem ); } Enter fullscreen mode Exit fullscreen mode Statt 2 × n_kv_heads × head_dim Elemente pro Schicht zu cachen, speichert MLA nur 512 + 64 = 576 Elemente pro Schicht, was den KV-Cache-Speicherbedarf im Vergleich zu äquivalenten GQA-Modellen um etwa 70% bis 80% reduziert.
Beim Dimensionieren von GPU-Nodes oder beim Prüfen von Token-Budgets wird diese Rechnerei von Hand über verschiedene Modelle und Quantisierungsschemata schnell mühsam. Ich habe eine Reihe von zu 100% clientseitigen, Zero-Trust interaktiven Rechnern gebaut (keine Backend-Aufrufe, keine Telemetrie), mit denen man seine exakte Workload modellieren kann:
Welches GPU-Setup nutzt ihr aktuell für lokale oder produktive LLM-Inferenz, und habt ihr FP8-KV-Cache-Quantisierung unter Langkontext-Lasten getestet? Lasst uns in den Kommentaren Erfahrungen austauschen!
Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.
Original (Englisch) anzeigen
Every engineer deploying open-weights models hits the same rite of passage:
You take a 70B parameter model, quantize it to 4-bit ( 70B * 0.5 bytes ≈ 35 GB ), provision a single 80GB A100 or H100 (or two 24GB GPUs), and launch vLLM or llama.cpp with a 64K context window and a small concurrent batch size.
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.50 GiB. Enter fullscreen mode Exit fullscreen mode How does a "35 GB" model run out of memory on an 80 GB GPU?
Because model weights are only the static floor of LLM memory. Once you factor in quantization scale-block overhead, prefill activation buffers, CUDA/PyTorch workspace reservations, and the silent giant— the Key-Value (KV) Cache —your real VRAM footprint can easily double or triple.
Here is the exact byte-level math behind production LLM VRAM allocation, how Grouped-Query Attention (GQA) and DeepSeek's Multi-Head Latent Attention (MLA) change the scaling curve, and how to size your GPU hardware before paying for cloud instances.
At runtime, total GPU memory consumption is governed by four distinct allocations:
Total VRAM = M_weights + M_kv_cache + M_activations + M_runtime_overhead Enter fullscreen mode Exit fullscreen mode Component Scaling Factor Typical Share (Short Context) Typical Share (128K Context / Batch 16) 1. Model Weights ( M_weights ) Parameters × Bits_Effective 75% – 85% 30% – 45% 2. KV Cache ( M_kv_cache ) Batch × Context_Len × Layers 5% – 15% 45% – 65% 3. Activation Buffers ( M_act ) Batch × Prefill_Chunk × Hidden_Dim 3% – 8% 5% – 10% 4. CUDA / Runtime Overhead Fixed + PagedAttention Block Tables 1.5 GB – 3.0 GB 2.0 GB – 4.0 GB 2. Why 4-Bit Quantization Is Never Exactly 0.5 Bytes/Param A common rule of thumb says INT4 = 0.5 bytes per parameter. In practice, naive uniform 4-bit quantization destroys model perplexity because weight distributions contain extreme outliers.
Modern quantization formats ( AWQ, GPTQ, GGUF K-Quants ) group weights into blocks (e.g., 32, 64, or 128 weights) and store higher-precision FP16 scales and zero-points per block, while keeping embedding and output head layers ( lm_head ) at 16-bit or 8-bit precision:
// Effective bits per parameter (bpw) in production formats const EFFECTIVE_BITS_PER_WEIGHT: Record < string, number > = { FP16: 16.0, // 2.00 bytes/param BF16: 16.0, // 2.00 bytes/param FP8: 8.5, // ~1.06 bytes/param (channel/tensor scaling factors) INT8: 8.5, // ~1.06 bytes/param INT4_AWQ: 4.5, // ~0.5625 bytes/param (group_size=128 FP16 scales) GGUF_Q5_K_M: 5.7, // ~0.71 bytes/param GGUF_Q4_K_M: 4.85 // ~0.606 bytes/param (super-blocks + unquantized tensors) }; export function calculateWeightVramGB ( paramsBillions: number, format: keyof typeof EFFECTIVE_BITS_PER_WEIGHT ): number { const bpw = EFFECTIVE_BITS_PER_WEIGHT [ format ]; return ( paramsBillions * 1 e9 * ( bpw / 8 )) / ( 1024 ** 3 ); } Enter fullscreen mode Exit fullscreen mode For a 70.6B model in GGUF Q4_K_M, weight memory alone is ~39.8 GB (GiB)—already 5 GB higher than the back-of-the-envelope 35 GB estimate.
During autoregressive generation, recomputing attention Keys ($K$) and Values ($V$) for all previous tokens at every step would make decoding $O(N^2)$. Instead, inference engines cache $K$ and $V$ tensors across all transformer layers.
For a model using Grouped-Query Attention (GQA) (such as Llama 3/4, Qwen 3, or Mistral), the KV cache size in bytes is:
KV_Bytes = 2 (K and V) × n_layers × n_kv_heads × head_dim × seq_len × batch_size × bytes_per_kv_element Enter fullscreen mode Exit fullscreen mode Let's plug in the real architectural constants for a 70B GQA model ( n_layers = 80, n_kv_heads = 8, head_dim = 128 ):
2 × 80 × 8 × 128 × 2 bytes = 327,680 bytes ≈ 320 KB per token Enter fullscreen mode Exit fullscreen mode Now scale to a 64K context window ( 65,536 tokens) with batch_size = 2: 320 KB × 65,536 × 2 = 41,943,040,000 bytes ≈ 39.06 GiB! Enter fullscreen mode Exit fullscreen mode Notice what just happened:
(And if the model had used classic Multi-Head Attention with 64 KV heads instead of 8 GQA heads, that same KV cache would have required **312.5 GiB!)
In DeepSeek-V3 and DeepSeek-R1 (671B total MoE parameters, 37B active), keeping standard GQA tensors across 61 layers at 128K context would bottleneck PCIe/NVLink bandwidth.
Instead, Multi-Head Latent Attention (MLA) projects Keys and Values jointly into a compressed low-rank latent vector ($c_t^{KV}$, typically dimension $d_c = 512$) plus a decoupled RoPE key dimension ($d_R = 64$):
export function calculateKvCacheBytesPerToken ( arch: { attentionType: ' GQA ' | ' MLA '; numLayers: number; numKvHeads?: number; headDim?: number; kvLoraRank?: number; // e.g. 512 for DeepSeek V3/R1 qkRopeHeadDim?: number; // e.g. 64 kvBytesPerElem: number; // 2 for FP16, 1 for FP8 KV cache }): number { if ( arch. attentionType === ' MLA ' ) { const compressedDim = ( arch. kvLoraRank?? 512 ) + ( arch. qkRopeHeadDim?? 64 ); // MLA caches only the joint latent vector per layer (no 2x K/V multiplier!) return arch. numLayers * compressedDim * arch. kvBytesPerElem; } return ( 2 * arch. numLayers * ( arch. numKvHeads??
8 ) * ( arch. headDim?? 128 ) * arch. kvBytesPerElem ); } Enter fullscreen mode Exit fullscreen mode Instead of caching 2 × n_kv_heads × head_dim elements per layer, MLA caches only 512 + 64 = 576 elements per layer—slashing KV cache memory by ~70% to 80% compared to equivalent GQA models.
When sizing GPU nodes or auditing token budgets, doing this math by hand across different models and quantization schemes gets tedious fast. I built a set of 100% client-side, zero-trust interactive calculators (no backend calls, no telemetry) that you can use to model your exact workload:
What GPU setup are you currently using for local or production LLM inference, and have you tested FP8 KV cache quantization under long-context loads? Let's compare notes in the comments!