Zum Inhalt springen
Kurzer Hinweis: flozi00 TechHub ist ein Solo-Nebenprojekt neben einem Vollzeitjob — persönliche Lernnotizen, keine offiziellen Aussagen. Kritische Schritte selbst prüfen.

Der KV-Cache: Exakte Speicher-Mathematik, GQA vs MQA vs MLA und PagedAttention

Warum der Key-Value-Cache Ihren GPU-Speicher auffrisst: die exakte Per-Token-Formel, MHA vs MQA vs GQA vs MLA mit berechneten Beispielen, PagedAttention-Block-Mathematik, RadixAttention-Präfix-Wiederverwendung, FP8-KV-Cache und ein durchgerechnetes Kapazitätsbeispiel — alles aus den Primärquellen belegt.

7 Min. Lesezeitflozi00
aimachine-learninggpugpu-memoryoptimizationdeep-learning

Jede LLM-Serving-Frage — wie viele Nutzer passen auf eine GPU, warum lange Kontexte kollabieren, was FP8-KV wirklich bringt — beantwortet ein einziger Tensor: der Key-Value-Cache. Dieser Leitfaden leitet seine exakte Größe auf Byte-Ebene her und geht dann die drei Angriffsachsen (Architektur, Allokator, Datentyp) durch — mit echten Zahlen für jede Aussage.

Die eine Formel

Jede Transformer-Schicht speichert pro Token und KV-Head einen K- und einen V-Vektor — die gesamte Historie bei jedem Schritt neu zu berechnen wäre quadratisch, also wird sie einmal berechnet und bei jedem Schritt gelesen:

KV-Bytes=2×L×HKV×dhead×bdtype×s\text{KV-Bytes} = 2 \times L \times H_{\text{KV}} \times d_{\text{head}} \times b_{\text{dtype}} \times s
  • L = Schichten, H_KV = KV-Heads, d_head = Head-Dimension, b_dtype = Bytes pro Wert, s = Tokens
  • Faktor 2 = K und V werden getrennt gespeichert
  • Dieselbe Formel wie unser VRAM-Rechner; entspricht Gleichung 1 des PagedAttention-Papers (arXiv:2309.06180)

Referenzmodell in diesem Leitfaden (dieselbe site-verifizierte Konfiguration wie in der VRAM-Anleitung): Qwen3-VL-32B — 64 Schichten, 8 KV-Heads (GQA), Head-Dimension 128.

Pro Token bei fp16:

2×64×8×128×2 B=262.144 B=256 KiB≈0,26 MB2 \times 64 \times 8 \times 128 \times 2\,\text{B} = 262.144\,\text{B} = 256 \text{ KiB} \approx 0{,}26 \text{ MB}
KontextKV-Cache (fp16)KV-Cache (fp8)
8.192 Tokens2,15 GB1,07 GB
32.768 Tokens8,59 GB4,29 GB
65.536 Tokens17,18 GB8,59 GB
262.144 Tokens68,72 GB34,36 GB

Zum Vergleich: Die fp16-Gewichte dieses Modells belegen 66,7 GB (33.357.390.064 Parameter × 2 Bytes). Bei maximalem Kontext ist der KV-Cache eines einzigen Nutzers (68,72 GB) also bereits größer als das gesamte Modell — der Schnittpunkt liegt bei rund 254.500 Tokens. Kontext ist nicht kostenlos: Er ist linearer Speicher pro Nutzer, für jeden Nutzer, immer.

Warum er auch ein Bandbreitenproblem ist

Decode ist speicherbegrenzt: Ein Forward-Pass muss im Wesentlichen den gesamten Gewichtstensor plus den vollständigen KV-Cache dieses Nutzers aus dem HBM lesen, jedes Byte genau einmal verwenden und daraus ein einziges Token erzeugen. Auf einer H100 SXM (80 GB HBM3 mit 3,35 TB/s laut Datenblatt) ergibt das Intensitätsmodell eine Obergrenze:

  • Batch 1, 8k Kontext: (65 + 2,15) GB / 3,35 TB/s ≈ 20,0 ms → 49,9 Tok/s Obergrenze für einen Nutzer
  • Batch 16, je 8k: (65 + 34,4) GB / 3,35 TB/s ≈ 29,7 ms pro Schritt, aber das Lesen der Gewichte wird geteilt → 33,7 Tok/s pro Nutzer, 539 Tok/s aggregiert — 10,8× mehr Gesamtdurchsatz

Das ist die gesamte Ökonomie des Batching: Die Gewichts-Lesevorgänge über mehr Sequenzen amortisieren. Jeder Nutzer wartet ~48 % länger pro Token als allein (29,7 ms statt 20,0 ms), und die Karte produziert fast elfmal so viele Tokens. (Dieses lineare Bandbreitenmodell ignoriert Attention-FLOPs, Cache-Treffer und Kernel-Overhead — es ist eine Obergrenze, kein Benchmark.)

Bei 32k Kontext, Batch 1: (65 + 8,59) GB pro Schritt → 45,5 Tok/s — allein das Lesen des Caches kostet jeden Schritt 2,6 ms und wächst mit jedem generierten Token.

Achse 1: Architektur — MHA vs MQA vs GQA vs MLA

Die Architektur bestimmt, wie viele K/V-Vektoren jeder Token kostet. Vier Familien, Per-Token-Cache beim Referenzmodell (fp16):

VarianteKV-Paare pro TokenPro Token (fp16)16 Nutzer × 32k Kontext
MHA — jeder Query-Head besitzt eigene K/V642.097.152 B (2 MiB)1.099,5 GB
MQA — ein gemeinsamer K/V-Head 1132.768 B (32 KiB)17,2 GB
GQA — ein gemeinsamer K/V pro Query-Gruppe 28262.144 B (256 KiB)137,4 GB
MLA — komprimiertes Latent statt per-Head-K/V 3—70.272 B ≈ 68,6 KiB (DeepSeek-V3, 61 Schichten)36,8 GB (DeepSeek-V3)

Diese Byte-Zahlen sind das, was die Architektur-Namen bedeuten. MQA teilt ein K/V-Head-Paar über alle 64 Query-Heads — in den Worten des Papers: „a single set of keys and values shared across heads“ — und reduziert den Cache um 64×. GQA liegt dazwischen: eine feste Zahl von KV-Gruppen, hier 8 von 64 Query-Heads, also ein 8-facher Schnitt; das Kernergebnis des Papers ist Qualität nahe MHA bei Geschwindigkeit nahe MQA, plus ein „Uptraining"-Rezept, das bestehende MHA-Checkpoints mit etwa 5 % der ursprünglichen Pretraining-Rechenleistung umwandelt. MLA ist ein völlig anderer Mechanismus: Statt K und V pro Head zu speichern, wird ein rank-512-komprimiertes Latent plus ein 64-dimensionaler entkoppelter RoPE-Key gespeichert — 576 Elemente pro Token pro Schicht in DeepSeek-V3s publizierter Konfiguration — und die vollen K/V werden während der Attention on-the-fly rekonstruiert. Das V2-Paper berichtet einen um 93,3 % kleineren Cache und 5,76× höheren maximalen Generierungs-Durchsatz gegenüber seiner 67B-MHA-Baseline.

Für die Kapazitätsplanung wählen Sie die Attention-Variante selten selbst — Sie wählen ein Modell, und die Variante kommt mit. Der nutzbare Takeaway: zu wissen, was Sie pro Token gekauft haben, aus der Tabelle oben.

Achse 2: Der Allokator — zusammenhängende Reservierung vs PagedAttention

Die zweite Achse hat mit dem Modell gar nichts zu tun. Eine Serving-Engine muss die wachsenden (k,v)-Zeilen jeder Anfrage irgendwo im VRAM unterbringen. Das naive Schema — ein zusammenhängender Puffer in Maximalgröße pro Anfrage — verschwendet enorme Speichermengen: Reservierungen werden selten gefüllt, und zusammenhängende Blöcke fragmentieren, wenn Anfragen unterschiedlicher Länge kommen und gehen.

Das PagedAttention-Paper (Kwon et al., SOSP 2023, arXiv:2309.06180) hat das gemessen: In den damaligen Standard-Systemen speicherten nur 20,4–38,2 % des für KV-Caches allokierten Speichers tatsächlich Token-Zustände — der Rest war Reservierungs-Slack und Fragmentierung. vLLMs paged Allokator, inspiriert vom virtuellen Speicher von Betriebssystemen, erreichte im selben Experiment 96,3 % effektive Nutzung: Logische Per-Anfrage-Caches werden in feste 16-Token-Blöcke zerlegt (vLLMs Standard-Blockgröße), über eine Blocktabelle pro Sequenz auf verstreute physische Blöcke abgebildet, bei Bedarf allokiert und Block für Block wieder freigegeben.

Block-Mathematik (Blockgröße 16): Eine 32.768-Token-Sequenz belegt 2.048 Blöcke; beim Referenzmodell hält jeder Block 16 × 262.144 B = 4 MiB K/V. Eine Anfrage, die bei 1.000 Tokens endet, zahlt für 63 Blöcke (1.000/16 → 62,5 → 63 mit Teil-Block), nicht für die 8,59 GB, die eine volle 32k-Reservierung festlegen würde. Der Verschnitt sinkt von „das meiste des Puffers" auf „höchstens ein teilweise gefüllter Block pro Anfrage".

Präfix-Wiederverwendung — RadixAttention und Verwandte

Paging behebt die Fragmentierung; ein zweiter Allokator-Gewinn ist das Teilen. Prompts stecken voller wiederholter Präfixe — derselbe System-Prompt für jeden Nutzer, dieselben Few-Shot-Beispiele über einen Benchmark hinweg, das gesamte vorherige Gespräch im Multi-Turn-Chat. Wenn jede Anfrage ihre eigene Kopie dieser Präfix-KV neu berechnet und speichert, zahlen Sie Rechenleistung und Speicher für dieselben Bytes immer wieder.

SGLangs RadixAttention (arXiv:2312.07104) behält fertige und laufende KV in einem Radix-Baum, verschlüsselt nach Token-Sequenzen, mit LRU-Verdrängung und referenzgezählten Knoten (die Baumstruktur selbst liegt auf der CPU). Ein neuer Prompt, der ein Präfix mit irgendetwas im Baum teilt, verwendet das KV dieses Zweigs wieder, ohne Neuberechnung: Die gemeinsamen Anteile werden automatisch gefunden, keine manuelle Präfix-Konfiguration. Das Paper berichtet bis zu 6,4× höheren Durchsatz bei präfix-lastigen Workloads gegenüber Systemen ohne automatische Wiederverwendung; der Baum-Lookup selbst verursacht vernachlässigbaren Overhead.

Präfix-Mathematik (Referenzmodell): Ein 2.000-Token-System-Prompt kostet 2.000 × 262.144 B ≈ 524 MB KV pro Gespräch bei fp16 — ohne Präfix-Sharing für jeden gleichzeitigen Nutzer neu berechnet und neu gespeichert. Mit Baum-Sharing wird er einmal berechnet und einmal gespeichert. Multi-Turn-Chat ist der Extremfall: Runde n teilt das gesamte Präfix der Runden 1…n−1, die inkrementellen KV-Kosten jeder Runde sind also nur ihre neuen Tokens.

Achse 3: Datentyp — FP8-KV-Cache

Die dritte Achse halbiert die Byte-Kosten pro Wert: Architektur beibehalten, Allokator beibehalten, den Cache in FP8 speichern. vLLM stellt dies als kv_cache_dtype="fp8" bereit (Optionen: auto, fp8_e4m3, fp8_e5m2; CUDA 11.8+), mit zwei Quantisierungsschemata — Per-Tensor-Skalen oder Skalen pro Attention-Head (q_scale = [num_heads], k/v_scale = [num_kv_heads]). Die vLLM-Dokumentation begründet das kapazitätsorientiert: FP8-KV verdoppelt näherungsweise die Zahl der Tokens, die in dasselbe Cache-Budget passen — das unterstützt entweder längere Kontexte oder mehr gleichzeitige Anfragen; mit FlashAttention-3 läuft die Attention vollständig quantisiert. Kalibrierung: keine (Skalen = 1,0), on-the-fly Random-Token-Kalibrierung oder Datensatz-Kalibrierung via llm-compressor (empfohlen).

Durchgerechnetes Kapazitätsbeispiel (H100 80 GB): Budget = 80 × 0,9 = 72 GB. Gewichte fp16 = 66,7 GB → 5,3 GB frei → fp16-KV: 20.162 Tokens; fp8-KV: 40.323 Tokens. Dieselbe GPU, quantisierte Gewichte (AWQ/GPTQ ≈ 18,7 GB) → 53,3 GB frei → fp16-KV: 203.399 Tokens; fp8-KV: 406.798 Tokens. Der maximale Kontext des Referenzmodells ist 262.144 Tokens: Mit fp16-Gewichten passt nicht einmal ein einziger Max-Kontext-Nutzer; quantisierte Gewichte + FP8-KV halten ihn mit Reserve.

Was Sie mitnehmen sollten

  • Eine Formel: KV-Bytes = 2 × L × H_KV × d_head × b_dtype × s. Alles andere ist abgeleitet.
  • Die Architektur kommt mit dem Modell: GQA-8 → 256 KiB/Token fp16; MQA → 32 KiB; MLA → ~70 KB (DeepSeek-V3). Wissen, was Sie gekauft haben.
  • Paging schlägt Planung: Naive zusammenhängende Reservierung verschwendet bis zu ~80 % des KV-Budgets (20,4–38,2 % effektiv); PagedAttention-artige Allokatoren erreichen 96,3 %.
  • Präfix-Wiederverwendung ist gratis: Ein 2.000-Token-System-Prompt kostet ≈ 524 MB KV beim Referenzmodell — einmal über einen Radix-Baum über Nutzer geteilt, statt pro Nutzer neu berechnet.
  • FP8 halbiert die Per-Token-Kosten: von 256 KiB auf 128 KiB — Verdopplung der Kapazität für dasselbe Budget, lt. vLLM-Feature-Dokumentation.
  • KV kreuzt die Gewichte bei ~254.500 Tokens beim Referenzmodell (fp16): Darunter dominieren die Gewichte das Budget, darüber der Cache — und Gewichts-Quantisierung bringt am meisten, wenn der Cache dominiert (beide Segmente oben im Beispiel gerechnet).
  • Decode ist speicherbegrenzt: Batch-1-Decode liest pro Token die gesamten Gewichte + den Cache dieses Nutzers: ~50 Tok/s Ceiling auf H100 SXM bei 8k Kontext; Batching von 16 Nutzern macht aus einem Gewichts-Lesevorgang 10,8× aggregierten Durchsatz.
  • Weiterlesen: LLM-VRAM-Bedarf (Gewichte + KV-Cache vollständig), Inferenz-Mathematik (warum Decode speicherbegrenzt ist), Quantisierung (die andere Hälfte des Speicherproblems).

Footnotes

  1. Shazeer, Fast Transformer Decoding: One Write-Head is All You Need, arXiv:1911.02150 (2019). ↩

  2. Ainslie et al., GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints, arXiv:2305.13245, EMNLP 2023. Llama-2-70B liefert 64 Query- / 8 KV-Heads. ↩

  3. DeepSeek-V2, arXiv:2405.04434: 93,3 % KV-Cache-Reduktion, 5,76× Generierungs-Durchsatz. Per-Token-Zahlen aus DeepSeek-V3s config.json (kv_lora_rank 512 + qk_rope_head_dim 64 = 576 Elemente, 61 Schichten, bf16 → 70.272 B/Token ≈ 68,6 KiB). Vergleich Llama-3.1-70B GQA-8: 2 × 8 × 128 = 2.048 Elemente/Schicht × 80 Schichten × 2 B = 327.680 B ≈ 320 KiB, also das 4,7-fache des DeepSeek-V3-MLA-Footprints. ↩