Aha!

Warum dein 8-GB-Modell 20 GB Speicher braucht

Die Dateigröße ist nicht der Speicherbedarf. Im Betrieb kommt der KV-Cache dazu - und der hängt nicht am Modell, sondern am Kontext und an der Zahl gleichzeitiger Anfragen.

6. August 2026vram · kv-cache · inferenz

Man lädt ein Modell herunter, sieht eine Zahl neben dem Dateinamen und rechnet damit. Die Q8_0-Datei von Qwen3-VL-8B-Instruct ist 8,5 GB groß - also passt sie mit reichlich Luft auf eine Karte mit 24 GB. Der Server startet auch tatsächlich, antwortet, alles gut. Dann kommt der zweite Nutzer, jemand wirft ein längeres Dokument hinein, und plötzlich steht CUDA out of memory im Log.

Die Dateigröße war nie der Speicherbedarf. Sie ist nur der eine Posten, der feststeht.

Drei Posten, und nur einer ist konstant

Was eine Inferenz-Engine auf der Karte belegt, sind drei Dinge:

  • Die Gewichte. Das ist die Datei. 8,5 GB auf der Platte sind 7,9 GB im Speicher - die Platte zählt dezimal, der Treiber binär. Diese Zahl ändert sich nie, egal was passiert.
  • Der Overhead. CUDA-Kontext, Arbeitspuffer, Verschnitt des Allokators. Knapp ein Gigabyte plus ein paar Prozent auf den Rest. Lästig, aber überschaubar.
  • Der KV-Cache. Der Posten, den niemand auf dem Zettel hat - und der einzige, der wächst, während der Server läuft.
Live-ExperimentSpalten = Tokens · Bänder = Layer

Cache-Matrix

Jede Spalte der Matrix ist ein Token, jedes Band eine Gruppe von Layern. Was einmal berechnet ist, bleibt stehen und wird von da an nur noch gelesen. Der Prefill füllt alle Prompt-Spalten in einem Durchgang, danach hängt jeder Decode-Schritt genau eine Spalte an - mitzählen kann man das unten.

Beispiel

Prompt als Tokens

  1. Du
  2. ·bist
  3. ·ein
  4. ·knapp
  5. er
  6. ·Assistent
  7. .
  8. Warum
  9. ·ist
  10. ·der
  11. ·erste
  12. ·Token
  13. ·langsam
  14. ?

14 Prompt-Tokens, 0 erzeugt · Punkte stehen für Leerzeichen · die Zerlegung ist ein festes Beispiel, kein Live-Tokenizer

K/V-Matrix

0 von 24 Spalten belegt

  • gerade berechnet
  • gecached, wird nur gelesen
  • aus der vorigen Anfrage übernommen
  • noch leer

Die 6 Bänder stehen stellvertretend für die 32 Layer des Referenzmodells - in jedem einzelnen liegt ein eigenes Schlüssel-Werte-Paar je Token.

Mit Cache: Der Prefill rechnet den Prompt einmal, jeder weitere Token hängt eine Spalte an. Ein Wechsel von Beispiel oder Modus beginnt eine frische Sitzung.

berechnet

0

Spalten seit dem Prefill

wiederverwendet

0

Spalten gelesen statt gerechnet

Cache belegt

0 KiB

128 KiB je Spalte

Aufwand dieser Anfrage

mit Cache (linear)
0 = 14 Prefill + 0 × 1
ohne Cache (quadratisch)
0 · letzter Schritt allein: 14

Die beiden Zahlen sind keine Schätzung gegeneinander: 0 berechnete plus 0 wiederverwendete Spalten ergeben genau die 0 Spalten des Falls ohne Cache. Der Cache erfindet keine Arbeit, er hebt sie auf - hier um den Faktor 1.

Speicher

Cache jetzt0 KiB · 0 Spalten
Volles Kontextfenster1,0 GiB · 4,2 % einer 24-GB-Karte

Referenzmodell: 32 Layer × 8 KV-Köpfe × 128 Dimensionen × 2 (Schlüssel und Werte) × 2 Byte (FP16) = 128 KiB je Token. Der untere Balken misst gegen 24 GiB Grafikspeicher.

BefundNoch nichts berechnet. Der Prompt steht da, die Matrix ist leer - genau der Zustand, in dem eine Anfrage beim Modell ankommt.

Geführte Schritte

Fünf Klicks, fünf Beobachtungen. Der Knopf stellt das Labor jeweils passend ein; die Erkenntnis öffnet sich, sobald das Gezeigte einmal auf dem Schirm war.

Was hier üblicherweise falsch verstanden wird

Das Modell liest bei jedem Token den ganzen Text neu.
Den Text nicht - dessen Repräsentationen liegen ja schon in der Matrix. Neu gerechnet wird genau eine Spalte, alles davor wird gelesen. Der Schalter „ohne Cache“ zeigt, was die andere Lesart kosten würde.
Der KV-Cache ist ein Antwort-Cache.
Er speichert Zwischenzustände einer laufenden Generierung, keine Ergebnisse. Zweimal dieselbe Frage erzeugt zwei Generierungen - der Cache spart den Prefill, nicht das Antworten.
Das Kontextfenster ist nur eine Software-Grenze.
Es hat eine physische Seite: Jedes Token im Fenster belegt seinen Platz in dieser Matrix. Der untere Balken zeigt, wie viel Grafikspeicher ein volles Fenster allein dafür braucht.

Der Cache ist kein Luxus, den man abschalten könnte. Ohne ihn müsste das Modell für jedes einzelne neue Token die Aufmerksamkeit über den kompletten bisherigen Text neu berechnen. Der Cache hält Keys und Values aller bisherigen Tokens fest, damit jeder Decode-Schritt nur noch eine Spalte dazurechnet statt alles noch einmal. Er ist der Grund, warum die Antwort nach der ersten Pause gleichmäßig tröpfelt statt immer langsamer zu werden. Man bezahlt ihn in Speicher.

Die Formel, an einem echten Modell

Pro Layer und pro Token speichert ein Modell:

2 (Keys und Values) × num_key_value_heads × head_dim × Byte pro Element

Für Qwen3-VL-8B-Instruct stehen die Werte in der config.json des Repos: 36 Layer, 32 Aufmerksamkeitsköpfe, aber nur 8 KV-Köpfe, head_dim 128. Der KV-Cache liegt üblicherweise in FP16, also 2 Byte je Element:

2 × 8 × 128 × 2 Byte  =  4 096 Byte je Layer und Token
× 36 Layer            =  147 456 Byte  =  144 KiB je Token

144 KiB klingt nach nichts. Der Punkt ist, womit diese Zahl multipliziert wird: mit der Kontextlänge und mit der Zahl gleichzeitiger Anfragen. Beide Stellgrößen gehören nicht zum Modell. Sie gehören zur Nutzung.

Kontext je AnfrageGleichzeitige AnfragenKV-CacheGesamt auf der Karte
4 09610,6 GB9,8 GB
32 76814,5 GB13,9 GB
32 76829,0 GB18,7 GB
24 576310,1 GB19,9 GB
32 768418,0 GB28,3 GB
131 072118,0 GB28,3 GB

(Gewichte 7,9 GB und Overhead sind in der letzten Spalte enthalten.)

Drei Nutzer mit je 24k Kontext - ein längeres Dokument plus Gesprächsverlauf, im Alltag eines Agenten nichts Besonderes - und aus der 8-GB-Datei sind 20 GB Speicherbedarf geworden. Der Cache ist an dieser Stelle größer als das Modell. Bei vollem Kontextfenster geht die Rechnung ohnehin nicht mehr auf: Die 262 144 Tokens, die auf der Model Card stehen, kosten für eine Anfrage 36 GB - allein an Cache.

Und die letzten beiden Zeilen sind bewusst identisch. Dem Cache ist es egal, ob ein Nutzer 128k belegt oder vier Nutzer je 32k. Er kennt nur das Produkt.

Was daraus folgt - drei Dinge, die sonst zusammenhanglos wirken

Warum GQA erfunden wurde. In der Formel steht num_key_value_heads, nicht num_attention_heads. Bei diesem Modell sind das 8 statt 32: Vier Aufmerksamkeitsköpfe teilen sich einen Satz Keys und Values. Das ist Grouped-Query Attention, und der Effekt ist genau der Faktor 4. Ohne GQA - also mit klassischer Multi-Head Attention - läge derselbe Cache bei 576 KiB je Token, und die Zeile „32k Kontext, zwei Anfragen“ stünde nicht bei 9 GB, sondern bei 36. GQA ist keine Qualitätsverbesserung. Es ist die Antwort auf diese Tabelle.

Warum vLLM den Cache seitenweise verwaltet. Eine Engine weiß beim Start nicht, wie lang die Antworten werden. Der naive Weg ist, für jede Anfrage den Platz für das volle Kontextfenster zu reservieren - und dann liegen 36 GB belegt, während tatsächlich 2 000 Tokens drinstehen. PagedAttention zerlegt den Cache stattdessen in Blöcke fester Größe und verteilt sie wie Speicherseiten eines Betriebssystems: reserviert wird erst, wenn ein Block gebraucht wird, und identische Präfixe mehrerer Anfragen teilen sich dieselben Blöcke. Deshalb bedient dieselbe Karte unter vLLM ein Vielfaches der Anfragen, die eine Engine mit starrer Reservierung schafft.

Warum ein Server bei fünf Nutzern kippt, der bei einem lief. Die Gewichte liegen einmal da. Der Cache liegt pro Anfrage da. Ein Server, der mit einem Nutzer bei 13,9 GB steht, hat auf einer 24-GB-Karte scheinbar 10 GB Luft - in Wirklichkeit reicht die für genau zwei weitere Anfragen dieser Größe. Der Speicherbedarf skaliert linear mit der Gleichzeitigkeit, das gefühlte „läuft doch“ nicht.

Was man praktisch macht

Kontext kürzen ist die stärkste Schraube. Nicht das Kontextfenster des Modells, sondern das, was wirklich hineingeht. Jedes Token, das man aus dem Verlauf hält, spart 144 KiB - mal Anfragen, mal Laufzeit.

Das Fenster hart deckeln. In vLLM ist das --max-model-len. Der Wert begrenzt, wie viel Kontext eine einzelne Anfrage belegen darf, und macht damit aus einer unbekannten Obergrenze eine bekannte. Ohne ihn plant die Engine mit den 262 144 Tokens der Model Card - und startet auf einer 24-GB-Karte gar nicht erst. Dazu gehört --max-num-seqs: die zweite Stellgröße der Tabelle, ebenfalls gedeckelt.

Die Architektur mitwählen, nicht nur die Parameterzahl. Modelle mit begrenztem Aufmerksamkeitsfenster halten nur einen Teil ihrer Layer auf ganzer Länge vor. Bei gemma-4-12B-it sind 40 von 48 Layern auf 1 024 Tokens begrenzt; ihr Cache hört dort auf zu wachsen. Ergebnis: Unter der Last „32k Kontext, zwei Anfragen“ braucht das 12B-Modell 18,3 GB - etwas weniger als die 18,7 GB des 8B-Modells daneben. Bei 128k Kontext sind es 31,0 gegen 47,3 GB. Die kleinere Datei ist nicht automatisch der kleinere Server.

Den Cache quantisieren - mit Vorbehalt. FP8 statt FP16 halbiert den Posten exakt; aus 9,0 GB werden 4,5. Das ist die letzte Reserve und nicht die erste Maßnahme: Ein quantisierter KV-Cache kostet Qualität deutlich schneller als quantisierte Gewichte, weil jeder Fehler darin in alle folgenden Tokens weiterwirkt.

Wer das einmal gerechnet hat, liest Model Cards anders. Die Dateigröße sagt, ob das Modell auf die Karte passt. Ob der Server darauf passt, sagt sie nicht.

Passt dazu
Weitere Stücke