BLOG

PagedAttention von vLLM: Wie Speicherverwaltung die Inferenz großer Modelle trägt

Kael Zhang
AIvLLMInference
广告 · Advertisement

Technischer Deep-Dive: Analyse von AI-Frameworks – Beschreibung, Analyse, technische Bewertung, Werturteil und praktische Anwendung. Autor: Yongliang


Im Jahr 2023 gab es einen allgemein anerkannten Schmerzpunkt bei der Inferenz großer Modelle: Der GPU-Speicher schien groß genug zu sein, aber in der Praxis ließ sich die Batch-Size kaum erhöhen. Der Grund lag nicht in den Modellgewichten – diese sind statisch, man berechnet einmal und weiß, wie viel Platz sie brauchen – sondern im KV-Cache: Er bläht sich mit der Länge des Dialogs ständig auf und sieht bei jeder Anfrage anders aus. Das Sky Computing Lab der UC Berkeley lieferte in diesem Jahr eine Antwort, die später immer wieder zitiert wurde: Man verlagert die Paging-Mechanismen des virtuellen Speichers aus Betriebssystemen in die Attention-Berechnung. So entstand PagedAttention und die dahinterstehende Servicing-Engine vLLM.

Mehr als zwei Jahre später ist vLLM aus einer SOSP-Publikation zum De-facto-Standard für Inferenzdienste gewachsen: Stand September 2026 hat das Projekt auf GitHub etwa 92.000 Stars, wöchentlich rund 415.000 Downloads auf PyPI und mehr als 2000 Mitwirkende. Dieser Artikel zerlegt wie gewohnt sechs Aspekte: Was ist es, die Kernmechanismen bis auf Quellcode-Ebene, wie man Daten bewertet, die Auswahl zwischen vLLM und SGLang, ob es sich lohnt, wie man es einsetzt, und – falls Sie ein ähnliches Speichermanagement selbst schreiben wollen – was der minimale Ansatz dafür ist.

1. Was ist das

Ein Satz zur Einordnung: vLLM ist eine LLM-Inferenz- und Servicing-Engine mit hohem Durchsatz und Speichereffizienz (Originalbeschreibung im GitHub-Repository). Das Kernproblem, das sie löst, ist Folgendes: Der KV-Cache belegt den Großteil des Inferenzspeichers; wird er grob verwaltet, entsteht Verschwendung. Verschwendung drückt die Batch-Size, und die Batch-Size bestimmt direkt den Durchsatz.

Wichtige Fakten: Das Repository wurde im Februar 2023 erstellt und stammt ursprünglich vom Sky Computing Lab der UC Berkeley. Die Arbeit „Efficient Memory Management for Large Language Model Serving with PagedAttention“ wurde auf der Top-Konferenz SOSP 2023 im Bereich Systeme veröffentlicht (arXiv:2309.06180). Zu den neun Autoren gehören Größen wie Ion Stoica und Joseph Gonzalez aus dem Bereich verteilter Systeme. Lizenz: Apache-2.0. Etwa 91.598 Stars, 22.113 Forks. Die README beschreibt eine „Community aus Dutzenden akademischen Institutionen und Unternehmen“, und die Liste der Mitwirkenden ist nach Commit-Anzahl sortiert – die Top-100-Entwickler haben zusammen mehr als 12.000 Commits beigetragen. Die hohen Stars sind nicht gekauft, sondern das Ergebnis echter langfristiger Arbeit eines Teams.

2. Kernmechanismen

2.1 Erst die Rechnung machen: Wie viel Speicher frisst der KV-Cache wirklich?

PagedAttention ist kein bloßer Gag, sondern das Ergebnis einer vorherigen Kalkulation. Diese Rechnung lohnt sich heute noch für jeden, der mit Inferenz zu tun hat:

Bytes pro Token im KV-Cache = 2 × Layer-Anzahl × KV-Kopf-Anzahl × Kopf-Dimension × Bytes pro Parameter (Mal 2, weil K und V jeweils vorhanden sind).

Setzen wir die offizielle Konfiguration von Qwen2.5-72B ein (Hugging Face config.json): 80 Layer, GQA-Architektur mit 8 KV-Köpfen, Kopf-Dimension 128, fp16-Genauigkeit mit zwei Bytes pro Parameter – jeder Token belegt 327.680 Bytes, etwa 0,31 MB. Eine Anfrage mit 8k Kontext braucht allein für den KV-Cache 2,5 GB; bei hundert gleichzeitigen Anfragen sind es 250 GB. Hier leistet GQA einen großen Beitrag: 64 Attention-Köpfe teilen sich 8 KV-Köpfe. Wäre es noch die alte Multi-Head-Attention-Architektur, würde sich diese Zahl verachtfachen.

Diese Rechnung erklärt, warum die drei Arten von Verschwendung in der Arbeit so fatal sind. Reservierung: Alte Systeme reservieren auf Basis der geschätzten Maximallängen auf einmal einen zusammenhängenden Speicherblock für jede Anfrage; die meisten Positionen bleiben leer. Interne Fragmentierung: Die tatsächliche Länge erreicht die geschätzte Länge nicht, der Rest ist ungenutzt. Externe Fragmentierung: Der Speicher wird in Löcher unterschiedlicher Größe zerschnitten, sodass kein großer zusammenhängender Block mehr gefunden wird – die Anforderung nach zusammenhängendem Speicher bei alten Systemen ist eine selbstgegrabene Grube. Das experimentelle Fazit der Arbeit: In alten Systemen kann die effektive Nutzungsrate des KV-Cache auf nur etwa 20 % sinken; 80 % des Speichers sind umsonst belegt. Da Serving ein speicherlimitiertes Szenario ist, führt die direkte Folge dazu, dass die Batch-Size nicht groß und der Durchsatz nicht hoch werden kann.

2.2 PagedAttention: Drei Bauteile der Idee des virtuellen Speichers aus Betriebssystemen

Die Idee hinter PagedAttention ist erstaunlich direkt: Betriebssysteme verwalten virtuellen Speicher, indem sie den zusammenhängenden Adressraum, den ein Prozess sieht, in Seiten fester Größe zerschneiden und über eine Seitentabelle (Page Table) auf den physischen Speicher abbilden – warum nicht den KV-Cache genauso verwalten?

In der Umsetzung setzt es sich aus drei Teilen zusammen. Erstens, Paging: Der KV-Cache reserviert nicht mehr einen ganzen zusammenhängenden Speicherblock pro Anfrage, sondern wird in Blöcke fester Größe unterteilt (in den Experimenten der Arbeit oft 16 Token pro Block), die nur bei Bedarf zugewiesen werden. Zweitens, die Blocktabelle (block table): Jede Anfrage unterhält eine Zuordnungstabelle von logischen Blocknummern zu physischen Blocknummern. Der Attention-Kernel sammelt (gather) die entsprechenden physischen Blöcke basierend auf dieser Tabelle; die physischen Blöcke müssen nicht zusammenhängend sein. Dieser Schritt ist der Schlüssel des gesamten Textes – die Annahme der „Zusammenhängendheit“ ist genau die Quelle der externen Fragmentierung, und die Blocktabelle schafft sie vollständig ab. Drittens, bedarfsgesteuerte Zuweisung: Ein neuer Block wird erst beantragt, wenn der vorherige Block von links nach rechts gefüllt ist. Die Verschwendung pro Anfrage wird auf weniger als einen Block gedrückt, was nahezu Nullverschwendung bedeutet (so die Aussage der Arbeit).

2.3 Copy-on-Write: Referenzzählung für geteilte Blöcke

Paging bringt einen ungeplanten Vorteil mit sich: Sharing (Gemeinsame Nutzung). Bei parallelem Sampling (ein Prompt generiert n Kandidaten) oder mehreren Pfaden im Beam Search sind die Präfix-KV völlig identisch – alte Systeme speicherten sie entweder jeweils separat oder setzten auf komplexe Logik für ein hartes Sharing. vLLM macht es exakt wie das Betriebssystem: Der Referenzzähler des geteilten Blocks wird um eins erhöht. Wer schreiben will und der Referenzzähler ist größer als eins, kopiert zuerst und schreibt dann (Copy-on-Write). Das Ergebnis ist, dass Präfix-KV sowohl innerhalb als auch zwischen Anfragen wiederverwendet werden können. Die Arbeit listet dies als den zweitgrößten Beitrag von PagedAttention auf: Neben „near-zero waste“ kommt noch „flexible sharing“.

2.4 Quellcode-Ebene: V1 Block-Pool und Hash-Kette

Im Jahr 2025 hat vLLM die Neuschreibung der V1-Engine abgeschlossen; die gesamte Scheduler-Logik befindet sich nun im Verzeichnis vllm/v1, der Code der alten Engine wurde gelöscht. Der Einstiegspunkt für die Blockverwaltung ist BlockPool in vllm/v1/core/block_pool.py, der zwei Strukturen verwaltet:

Die erste ist die Freiblock-Warteschlange free_block_queue – eine doppelt verkettete Liste, sortiert nach „zuletzt verwendet“. Freigegebene Blöcke, die bereits wiederverwendet wurden, werden am Ende der Warteschlange wieder eingereiht; neue Zuweisungen nehmen vom Anfang der Warteschlange. Wenn der Block am Anfang noch Inhalte des Präfix-Cache hat, wird er vor der Zuweisung zuerst aus dem Cache verdrängt. Das LRU-Verdrängen (Least Recently Used) benötigt keine zusätzliche Datenstruktur; die Reihenfolge der Warteschlange selbst ist die Verdrängungsreihenfolge. Die zweite ist die Hash-zu-Block-Zuordnung cached_block_hash_to_block, die die Suche im Präfix-Cache unterstützt.

Der Hash eines Blocks ist nicht einfach ein „Hash des Inhalts dieses Blocks“, sondern verkettet. hash_block_tokens in kv_cache_utils.py sieht so aus: sha256(Hash des übergeordneten Blocks, Token-Sequenz dieses Blocks, zusätzlicher Schlüssel). Der Hash des übergeordneten Blocks dient als Salt – wenn sich der Präfixinhalt auch nur um ein Token unterscheidet, sind die Hashes der gesamten Kette dahinter komplett unterschiedlich; verschiedene Präfixe können nicht auf denselben Schlüssel stoßen. Die Funktion selbst hat einen LRU-Cache, sodass derselbe Blockinhalt nicht wiederholt berechnet werden muss. Wenn eine neue Anfrage reinkommt, vergleicht get_computed_blocks in kv_cache_manager.py ihren Prompt Block für Block mit dieser Hash-Kette. Bei Treffern wird der Referenzzähler des Blocks erhöht und direkt übernommen; der entsprechende Teil des Prefill wird komplett übersprungen.

Ein Detail im Quellcode ist erwähnenswert: Selbst wenn jeder Block des Prompt im Cache trifft, muss der letzte Token neu berechnet werden – denn für die Logits braucht man eine Live-Berechnung (Originalkommentar im Quellcode: „When all tokens hit the cache, we must recompute the last token to obtain logits“). Dieses kleine Design „bei Volltreffer trotzdem einen Schritt rechnen“ stellt sicher, dass das Cache-Treffen die Semantik der Ausgabe nicht verändert.

2.5 Scheduler: Eine Schleife frisst Prefill, Decode und Preemption

Der Einstiegspunkt für den Scheduler ist vllm/v1/core/sched/scheduler.py. Oben in dieser Datei gibt es einen Kommentar des Autors Woosuk, der sich lohnt, komplett zu lesen: Im Scheduler gibt es keine Unterscheidung zwischen „Decode-Phase“ und „Prefill-Phase“. Jede Anfrage verwaltet nur zwei Zahlen – num_computed_tokens (Anzahl der bereits berechneten Token) und num_tokens_with_spec (Position bis zu der berechnet werden soll, gleich Prompt-Länge plus bereits generierte Länge plus Spekulations-Token). Was der Scheduler in jedem Schritt tut, ist, jeder Anfrage ein Token-Kontingent zu geben, damit die erstere Zahl die letztere einholt.

Der Vorteil dieser Abstraktion ist, dass drei Szenarien von derselben Schleife einheitlich abgedeckt werden: Lange Prompts werden stückweise in den Batch gemischt (chunked prefill, eine lange Anfrage lässt nicht einen ganzen Schwarm von Decode-Requests verhungern); Anfragen mit Präfix-Cache-Treffer setzen direkt an der mittleren Position fort; beim spekulativen Dekodieren werden einfach ein paar Kandidaten-Token mehr berechnet, was nur num_tokens_with_spec ein Stück erhöht. Das Gesamtkontingent pro Schritt wird durch max_num_scheduled_tokens begrenzt.

Wenn der Speicher nicht reicht, ist das Mittel Preemption (Verdrängung): _preempt_request zieht Anfragen am Ende der running-Warteschlange zurück in die waiting-Warteschlange und gibt alle ihre Blöcke und den Encoder-Cache frei. Die Preemption in V1 kennt nur eine Art: Neu berechnen (recompute) – die zurückgezogene Anfrage wird beim nächsten Mal, wenn sie an der Reihe ist, komplett von vorne neu prefilled. Es wird kein Auslagern (Swap) in den CPU-Speicher durchgeführt. Das ist eine offenkundige Abwägung: Neu berechnen verschwendet Rechenleistung, aber die Implementierung ist einfach und vermeidet das Zittern beim Hin- und Herkopieren des KV-Cache zwischen CPU und GPU. In echten Online-Systemen ist die Häufigkeit von Preemptions ein Kennzahl, die man im Auge behalten sollte – häufige Preemptions deuten auf Probleme mit der Kapazitätsplanung oder den Scheduler-Parametern hin.

V1 macht noch zwei Arten von Überlappung (Overlap): Die Ausgabeverarbeitung (Detokenize, Streaming) überlappt sich mit der GPU-Berechnung; im asynchronen Scheduler-Modus überlappt sich die Vorbereitung der Scheduler-Entscheidung für den nächsten Schritt mit der Berechnung des aktuellen Schritts. Der Scheduler ist rein in Python geschrieben; solche Überlappungen sind sein kostenloses Mittagessen.

2.6 Von Blocktabelle zur GPU: Kernel, Backend und Multi-Prozess

Die Blocktabelle muss am Ende zu etwas auf der GPU werden: In jedem Schritt übergibt der Scheduler die block_table als Tensor an den Attention-Kernel. Für Decode verwendet vLLM den selbstgeschriebenen Paged-Attention-Kernel in csrc/attention mit CUDA/CUTLASS, der die physischen Blücke gemäß der Tabelle sammelt. Für Prefill ist die Attention-Form anders (berechnet den ganzen Prompt auf einmal), dieser Pfad wird nicht genutzt, sondern direkt auf vorhandene Backends wie FlashAttention oder FlashInfer zugegriffen. Darüber liegt die Abstraktionsschicht AttentionBackend; NVIDIA, AMD, CPU, TPU implementieren jeweils ihre eigenen Backends, und die Kernel-Details sind für den Scheduler unsichtbar.

Die Form des Decode-Schritts ist hochgradig regulär (pro Anfrage und Schritt genau ein Token mehr), weshalb V1 den gesamten Decode-Schritt mit CUDA Graphs einfängt, um die Startkosten zwischen Python und CUDA zu eliminieren. Szenarien mit Prefill- und Decode-Mischung verlassen sich auf die stückweise Kompilierung (piecewise compilation), um die Teile mit Formänderungen außerhalb des Graphen zu lassen. Dieser Schritt und das Paging ergänzen sich gegenseitig: Nur weil jede Anfrage den Speicher blockweise erhöht oder verringert, beeinflusst, wer im Batch geht und wer bleibt, nicht das Speicherlayout der anderen, sodass der gesamte Berechnungsschritt stabil als Graph eingefangen werden kann.

Schließlich das Multi-Prozess-Gerüst: Der API-Server-Prozess nimmt HTTP-Anfragen entgegen, übernimmt Tokenisierung und das Laden multimodaler Daten und kommuniziert über ZMQ mit dem Engine Core (Topologie „Many-to-Many“ mit mehreren API-Servern zu mehreren Engine Cores); der Engine-Core-Prozess trifft Scheduler-Entscheidungen; der GPU-Worker-Prozess läuft pro Karte, die Anzahl entspricht DP×PP×TP und ist nur für das Forwarding zuständig. Wenn Datenparallelität aktiviert ist, gibt es noch einen DP-Coordinator für den Lastausgleich. Die Standardbereitstellung für 4 Karten mit Tensorparallelität auf einem einzelnen Rechner ist: 1 API-Server + 1 Engine Core + 4 Worker, insgesamt 6 Prozesse.

3. Technische Bewertung: 2-4 Mal ist der Anspruch der Arbeit, schauen wir zuerst, womit verglichen wird

Die Daten der Arbeit: Bei gleicher Latenz liegt der Durchsatz von vLLM 2- bis 4-mal höher als bei den damals stärksten Systemen FasterTransformer und Orca; je länger die Sequenz, je größer das Modell und je komplexer der Dekodierungsalgorithmus, desto deutlicher der Vorteil (Originaltext der Arbeit). Achtung bei der Basislinie – das ist ein Vergleich aus dem Jahr 2023. Orca war damals bereits das fortschrittlichste System mit iterativem Scheduling. vLLM gewinnt gegen es durch Speichermanagement, nicht durch Operatoren.

Meine Interpretation hat zwei Ebenen. Die erste Ebene: Die Messung in der Arbeit, dass „die effektive Speichernutzung alter Systeme auf etwa 20 % sinken kann“, ist wesentlicher als die 2- bis 4-fache Steigerung – sie erklärt, warum die Verbesserung funktioniert: Wenn man die Verschwendung von 80 % auf weniger als einen Block drückt, passen in dieselbe Karte um ein Vielfaches mehr Anfragen, und der Durchsatz verdoppelt sich naturgemäß. Dieser Kausalzusammenhang ist hart. Die zweite Ebene: Im Koordinatensystem des Jahres 2026 kann man die 2- bis 4-fache Steigerung nicht mehr als Werbespruch abschreiben. Die heutigen Gegner sind SGLang, TensorRT-LLM und Co., der Abstand hat sich auf prozentuale Bereiche im Zusammenhang mit der Workload verringert.

Die Popularität lässt sich an drei Zahlen ablesen: Etwa 92.000 Stars; die Top-100 Mitwirkenden haben zusammen über 12.000 Commits; wöchentlich etwa 415.000 Downloads auf PyPI. Diese drei Zahlen greifen ineinander, der Umfang der tatsächlichen Nutzung ist unbestreitbar – es ist die Nummer-eine-Infrastruktur im Bereich Inferenz-Services.

Man muss jedoch auch Realitätschecks machen. Benchmarks sind Ansprüche aus der Arbeit; die eigene Reproduktion hängt von der eigenen Verteilung der Anfragelänge und dem Parallelitätsmuster ab. Der Präfix-Cache bringt bei Szenarien mit kurzen Anfragen und hoher Prompt-Vielfalt nur begrenzten Nutzen und belegt unnötig Speicher. Die Multi-Prozess-Architektur von V1 hat CPU-Overhead für kleine Modelle auf einer einzelnen Karte; die offizielle Dokumentation empfiehlt selbst, CPU-Ressourcen nach der Anzahl der GPUs zu dimensionieren.

4. Vergleich mit SGLang, wie wählt man aus

Das Profil von SGLang in einem Satz: Ein Hochleistungs-Servicing-Framework von LMSYS, erstellt im Januar 2024, etwa 36.000 Stars, Apache-2.0. Der Kernunterschied ist RadixAttention – es organisiert den Präfix-Cache als Radix-Baum statt als Hashtabelle. Offizieller Anspruch: „Bis zu 5-fache Inferenzbeschleunigung“ (offizieller Blog vom Januar 2024, eigene Daten). Im letzten Jahr wurde es häufig in Agent-Workflows und RL-Trainings-Frameworks (verl, slime usw.) eingesetzt.

Drei Sätze zur Auswahl: Wenn kein triftiger Grund besteht, ist vLLM die Standardwahl – das Ökosystem, die Dokumentation und die Hardware-Anpassung (NVIDIA/AMD/Intel/CPU First-Class-Support, TPU/Gaudi/Ascend/Apple Silicon über Plugins) sind am vollständigsten, und die Suchkosten für Problemlösungen sind am geringsten. Wenn Ihre Workload hauptsächlich aus Multi-Turn-Agent-Aufrufen, komplexen strukturierten Ausgaben besteht oder in RL-Trainingsschleifen eingebettet werden soll, lohnt es sich, SGLang zum Vergleich heranzuziehen; es hat in diesen Szenarien nachweisbare Vorteile durch aggressive Optimierungen. Beide sind Apache-2.0, die Migrationskosten sind nicht hoch, lassen Sie Ihren eigenen Traffic sprechen.

Konzeptionell stammen beide aus derselben Quelle: Das Präfix-Sharing von RadixAttention und PagedAttention sind zwei Datenstrukturen für denselben Gedanken (Radix-Baum vs. Blocktabelle-Hash), und das SGLang-Projekt selbst nutzt umfangreich die Infrastruktur von vLLM. Dies ist kein Nullsummenspiel, sondern die gesamte Branche konvergiert towards dem Konsens, dass „KV-Cache eine wiederverwendbare Ressource ist“.

5. Werturteil

Das echte Problem: Inferenzkosten gleich Speichereffizienz mal Batch-Size. vLLM hat den KV-Cache von einem „Reservierungssystem“ in ein „Paging-System“ verwandelt. Das ist meines Erachtens die wichtigste einzelne Verbesserung im Inferenz-Stack seit 2023 – sie verändert weder das Modell noch den Chip, sondern presst allein durch Systemsoftware aus derselben Hardware ein Vielfaches an Durchsatz heraus. Heute nutzen fast alle Open-Source-Inferenz-Frameworks diesen Gedanken der Blockverwaltung; das allein ist der Beweis für den Wert.

Die Grenzen sind ebenfalls klar. Wenn man kleine Modelle auf einem einzelnen Rechner mit einstelliger Parallelität betreibt, ist der Nutzen durch Paging und kontinuierliches Batching begrenzt; einfache Lösungen sind vielleicht entspannter. Es löst nicht die Latenz des ersten Tokens – der Engpass beim Prefill muss durch Kernel und Parallelisierungsstrategien angegangen werden, das ist ein anderes Schlachtfeld. Die Unterstützung für nicht-standardisierte Architekturen (wie Zustandsraummodelle à la Mamba) ist in V1 gerade erst mit der Schicht HybridKVCacheCoordinator gewachsen und noch in Entwicklung. Wann man es nutzen sollte: In jedem Szenario, in dem man LLM-Services ernsthaft anbieten will, ist es der Standard-Startpunkt. Wann man es nicht nutzen sollte: Für einmalige Offline-Läufe von ein paar Datenpunkten oder wenn man es im Training nutzen will – das ist das Revier anderer Tools.

6. Wie man es einsetzt

Die Installation in einer Zeile:

uv pip install vllm    # oder pip install vllm

Einen Service nach außen starten geht auch in einer Zeile, vllm serve startet eine OpenAI-kompatible Schnittstelle (gleichzeitig unterstützt es die Anthropic Messages API und gRPC):

vllm serve Qwen/Qwen3-32B --tensor-parallel-size 2

Für Offline-Batch-Inferenz nutzt man die Python-Klasse LLM, ein paar Zeilen bringen Ergebnisse. Es werden über 200 Modellarchitekturen unterstützt – Decoder-only, MoE, hybride Attention/Zustandsraum, Multimodal, Embedding, Belohnungsmodelle können alles serviert werden. Es deckt auch paralleles Sampling, Beam Search, strukturierte Ausgabe (xgrammar/guidance), Tool-Calling-Parsing und mehrere LoRAs ab. Auf der verteilten Seite sind Tensor-, Pipeline-, Daten-, Expert- und Kontext-Parallelität konfigurierbar. Zwei Ratschläge für die Praxis: Präfix-Caching sollte je nach Traffic-Merkmal ein- oder ausgeschaltet werden; bei Multi-Turn-Dialog-Lasten an, bei kurzen Anfragen mit hoher Vielfalt aus. In der Produktionsumgebung sollten die KV-Cache-Auslastung und die Anzahl der Preemptions in das Monitoring aufgenommen werden; diese beiden Kennzahlen decken Kapazitätsprobleme früher auf als die GPU-Auslastung.

7. Wie man selbst ein ähnliches System baut

„Selbst ein System bauen“ ist keine hypothetische Aufgabe. Das Projekt nano-vllm auf GitHub (GeeeekExplorer/nano-vllm, Open Source im Juni 2025, MIT, etwa 15.000 Stars) ist eine minimale lesbare Nachbildung, die die Community nach dem Lesen des vLLM-Quellcodes geschrieben hat – die gesamte Engine liegt im tausendzeiligen Bereich. Es beweist一件事: Der Kerngedanke von PagedAttention lässt sich auf einem Blatt Papier erklären. Das Gerüst umfasst sechs Schritte:

  1. Block-Pool plus Blocktabelle: Beim Start wird der KV-Cache-Speicher in einen Pool physischer Blöcke fester Größe zerteilt; jede Anfrage unterhält eine Tabelle von logischen Blöcken zu physischen Blöcken, und die Attention sammelt gemäß der Tabelle.
# Minimales Gerüst: Block-Pool
free_blocks = deque(range(num_gpu_blocks))   # Warteschlange für physische Freiblöcke
block_table = {}                             # req_id -> [Physische Blocknummern]
  1. Bedarfsgesteuerte Zuweisung: Nach jedem Decode-Schritt prüfen, ob der aktuelle Block voll ist; erst dann wird ein neuer Block aus der Freiwarteschlange geholt und angehängt – die Verschwendung überschreitet natürlich nie einen Block.

  2. Referenzzählung plus Copy-on-Write: Für geteilte Blöcke den Referenzzähler (refcount) erhöhen; vor dem Schreiben, wenn refcount größer als eins ist, zuerst kopieren und dann schreiben. Das Sharing bei parallelem Sampling und Beam Search basiert auf genau diesem Punkt.

  3. Scheduler-Schleife jagt nur zwei Zahlen: Jede Anfrage merkt sich num_computed und num_total; in jedem Schritt werden innerhalb des Token-Budgets Anfragen ausgewählt, damit computed total einholt; wer fertig ist, verlässt das Feld, wenn es nicht passt, wird nach Priorität verdrängt (Anfrage zurückziehen, Blöcke freigeben, später Prefill von vorne neu berechnen). Chunked Prefill, Präfix-Cache, spekulatives Dekodieren sind alles nur Spezialfälle dieser Schleife.

  4. Präfix-Hash-Cache: Eine verkettete Hash-Kette nach sha256(Hash des übergeordneten Blocks + Token dieses Blocks) aufbauen, Block für Block vergleichen, bei Treffer Prefill überspringen; der Kopf der Freiwarteschlange ist der Kandidat für die Verdrängung.

  5. Kernel nicht selbst schreiben: Attention direkt an FlashAttention andocken; CUDA/CUTLASS überlassen wir den Leuten, die ein echtes Performance-Team haben. So macht es nano-vllm auch.

Diese sechs Schritte zusammen sind ein paar hundert Zeilen Python plus fertige Kernel, und es läuft – die Kern-Einsicht der PagedAttention-Arbeit war nie komplex. Schwierig ist es, das zwei Jahre lang wie vLLM auf Produktionsniveau zu halten. Das ist auch der Grund, warum der vLLM-Quellcode mehr wert ist als die Arbeit: Die Architektur-Idee lässt sich auf einem Blatt Papier erklären, aber die technische Vollendung ist der Burggraben.

Fazit

Der Beitrag von PagedAttention lässt sich in einem Satz zusammenfassen: KV-Cache ist nicht „ein zusammenhängendes Array pro Anfrage“, sondern „eine Pool-Ressource, die bedarfsgesteuert zugewiesen und blockweise geteilt wird“. vLLM löst mit der alten Methode von Betriebssystemen ein neues Problem der Inferenz großer Modelle und ist allein durch diese Sache zum De-facto-Standard geworden. Für die meisten Teams braucht die Auswahl keine Zögern – Standard ist vLLM; bei speziellen Workloads SGLang zum Vergleich heranziehen. Für Ingenieure, die Inferenzsysteme bauen wollen, ist der Quellcode ein besseres Lehrbuch als die Arbeit.

Referenzen

  • vLLM GitHub-Repository (README, Architektur-Dokumentation): https://github.com/vllm-project/vllm
  • Arbeit: Efficient Memory Management for Large Language Model Serving with PagedAttention, SOSP 2023, arXiv:2309.06180 (Zusammenfassung, §2 Verschwendungsanalyse, §6 Bewertung)
  • Offizielle Architektur-Dokumentation von vLLM Architecture Overview (docs.vllm.ai, V1 Multi-Prozess-Architektur und Quellcode-Index)
  • vLLM V1 Quellcode: vllm/v1/core/block_pool.py (Block-Pool und Freiwarteschlange), vllm/v1/core/kv_cache_utils.py (hash_block_tokens verketteter Hash), vllm/v1/core/kv_cache_manager.py (get_computed_blocks Präfix-Treffer), vllm/v1/core/sched/scheduler.py (Scheduler-Hauptschleife und _preempt_request Verdrängung), csrc/attention (paged attention CUDA/CUTLASS Kernel)
  • Qwen2.5-72B Modellkonfiguration (Hugging Face config.json: 80 Layer / GQA 8 KV-Köpfe / Kopf-Dimension 128, Grundlage der KV-Cache-Speicherrechnung)
  • GitHub-Repository-Metadaten und Mitwirkendenliste, pypistats.org vllM Download-Zahlen (September 2026)
  • nano-vllm minimale Nachbildung (GitHub: GeeeekExplorer/nano-vllm, MIT): https://github.com/GeeeekExplorer/nano-vllm
  • Sekundärliteratur zur Quellcodeanalyse (zur Kreuzreferenz): Blogpark-Serie „Nano-vLLM Quellcodeanalyse“, Juejin-Serie „KV Cache und Paged Attention von nano-vllm“, CSDN-Serie „Lernnotizen zur Inferenz-Engine vLLM“ (V1-Architektur und Prefix Caching)
  • SGLang GitHub-Repository (README und Metadaten): https://github.com/sgl-project/sglang; LMSYS Blog 2024-01-17 (RadixAttention offizieller Anspruch)
广告 · Advertisement

Häufige Fragen

Wie löst PagedAttention das Problem der Speicherverschwendung im KV-Cache?

PagedAttention unterteilt den KV-Cache in Blöcke fester Größe, die dynamisch nach Bedarf zugewiesen werden. Dies vermeidet die Reservierungsverschwendung, interne Fragmentierung und externe Fragmentierung, die bei herkömmlichen Methoden durch die Reservierung von zusammenhängendem Speicher für jede Anfrage entstehen. Tests zeigen, dass die effektive Speichernutzung bei herkömmlichen Methoden auf bis zu 20 % sinken kann, während PagedAttention die Verschwendung auf weniger als einen Block komprimiert, sodass dieselbe GPU mehr gleichzeitige Anfragen bedienen kann.

Wie funktioniert der Präfix-Cache von vLLM?

vLLM verwendet eine verkettete Hash-Algorithmus, um KV-Cache-Blöcke zu indizieren. Der Hash-Wert jedes Blocks wird aus dem Hash des übergeordneten Blocks und den Token des aktuellen Blocks berechnet. Wenn der Prompt einer neuen Anfrage mit einem Präfix im Cache übereinstimmt, kann der bereits berechnete KV-Cache direkt wiederverwendet werden, wodurch ein Teil der Prefill-Berechnung übersprungen und die Latenz des ersten Tokens reduziert wird. Die Cache-Blöcke werden mit einer LRU-Strategie (Least Recently Used) verwaltet, wobei kürzlich nicht verwendete Blöcke zuerst entfernt werden.

Wie wählt man zwischen vLLM und SGLang?

Standardmäßig ist vLLM die Wahl, da es über ein umfassenderes Ökosystem, bessere Dokumentation und Hardware-Unterstützung verfügt und die Community sehr aktiv ist, was die Fehlerbehebung kostengünstig macht. Wenn die Workload hauptsächlich aus Multi-Turn-Agent-Aufrufen, komplexen strukturierten Ausgaben oder der Einbindung in RL-Trainingsschleifen besteht, lohnt sich ein Blick auf SGLang, da es für diese Szenarien gezielte Optimierungen bietet. Beide Projekte stehen unter der Apache-2.0-Lizenz; es wird empfohlen, Vergleiche mit echtem Traffic durchzuführen, bevor eine Entscheidung getroffen wird.