ARCHITEKTUR
Die Serving-Schicht für lokale LLMs: vLLM, SGLang, llama.cpp und Ollama
Ist das Modell gewählt, lautet die erste Architekturentscheidung, welche Serving-Schicht es betreibt. Dieser Beitrag trennt vier Schichten nicht über Funktionslisten, sondern über ihren architektonischen Ansatz bei Scheduling, Speicher, Wiederverwendung und Paketierung.
Wenn ein Unternehmen das Modell ausgewählt hat, das es betreiben will, hält es zunächst eine Datei in den Händen: Gewichte, ein Vokabular und das Prompt-Format, das das Modell erwartet. Aus dieser Datei einen Dienst zu machen, ist Aufgabe einer eigenen Software. Diese Schicht reiht eingehende Anfragen ein, teilt den GPU-Speicher zu, bestimmt, wie viele Nutzer gleichzeitig ein Gespräch führen können, und stellt nach aussen eine API bereit.
Dieser Beitrag trennt vier Namen — vLLM, SGLang, llama.cpp und Ollama — nicht über eine Funktionstabelle, sondern über ihren architektonischen Ansatz entlang von vier Achsen: Scheduling, Speicher, Wiederverwendung und Paketierung. Eine Rangfolge nach Geschwindigkeit entsteht dabei nicht, und jede Zahl weiter unten wird mit dem Datum und der Vergleichsbasis ihrer eigenen Quelle genannt. Die Berechnungen auf der Seite der Modelldatei — Quantisierungsstufen, VRAM-Arithmetik und Standardwerte für das Kontextfenster — gehören zur Modellauswahl und werden hier nicht wiederholt.
Was die Serving-Schicht leistet, was die Modelldatei leistet
Zwei Zuständigkeiten trennen sich. Die Modelldatei trägt die Gewichte, das Vokabular und das Prompt-Format. Die Serving-Schicht übernimmt Scheduling, Speicherverwaltung, Nebenläufigkeit und die API-Oberfläche. Die Trennung hat eine konkrete Folge: Dieselbe Modelldatei läuft auf allen vier Schichten, doch wie viele Nutzer auf derselben Hardware gleichzeitig eine Antwort erhalten, entscheidet die Serving-Schicht und nicht die Datei.
In der Praxis nimmt die Schicht meist die Gestalt eines HTTP-Servers an: Die Server-Dokumentation von llama.cpp beschreibt das eigene Werkzeug als leichtgewichtigen, reinen C/C++-HTTP-Server auf Basis von httplib und nlohmann::json, der REST-APIs und eine Web-Oberfläche bereitstellt. Auch die Unterbauten überschneiden sich — am 15. Mai 2025 kündigte Ollama für multimodale Modelle eine eigene Engine auf der GGML-Tensorbibliothek an und hielt im selben Beitrag fest, sich bis dahin bei der Modellunterstützung auf das Projekt llama.cpp gestützt zu haben.
Batching-Ansätze: statisch, dynamisch und kontinuierlich
Die erste Entscheidung einer Serving-Schicht betrifft die Frage, wie gleichzeitig eintreffende Anfragen gruppiert werden.
- Fester Batch: Anfragen werden zu einer Menge gesammelt, als Ganzes ausgeführt, und der nächste Batch startet erst, wenn jede Anfrage darin fertig ist. Diese Bezeichnung ist rein beschreibend und stammt aus keiner Quelle.
- Dynamisches Batching: Der Batch entsteht zur Laufzeit im Server. Anfragen warten kurz und werden abgeschickt, sobald die bevorzugte Grösse erreicht oder das Wartebudget aufgebraucht ist.
- Kontinuierliches Batching: Der Inhalt des Batches wird bei jeder Iteration neu bestimmt. Eine fertige Anfrage verlässt ihn, eine wartende kommt hinzu, sobald die laufende Iteration abgeschlossen ist.
Die dokumentierte Form des mittleren Ansatzes findet sich in NVIDIA Triton (siehe Triton Inference Server — Dynamic Batcher). Dynamisches Batching fasst einzelne Anfragen zu einem Zeitpunkt, den der Server selbst wählt, in einem einzigen Batch zusammen. Die Wartezeit begrenzt max_queue_delay_microseconds; die Beispielkonfiguration verwendet 100 Mikrosekunden.
Die primäre Quelle für den dritten Ansatz ist Orca (USENIX OSDI ’22, S. 521-538). Die Arbeit stellt zuerst den Befund fest: Bis dahin planten die Systeme die Ausführung auf Anfragegranularität und hielten eine feste Menge von Anfragen, bis jede Anfrage im Batch fertig war; eine früh beendete Anfrage konnte deshalb nicht an den Client zurückkehren, und eine später eintreffende Anfrage wartete, bis der laufende Batch vollständig abgearbeitet war. Orcas Antwort besteht darin, das Scheduling auf Iterationsgranularität herunterzuziehen: Der Scheduler wählt die auszuführenden Anfragen aus, ruft die Engine für eine einzelne Iteration auf und sammelt die Ergebnisse ein. Das ist der Ursprung dessen, was heute Continuous Batching heisst.
Die Arbeit definiert eine zweite Technik: selektives Batching. Die jeweils nächste Iteration zweier Anfragen lässt sich nicht immer zusammenführen — stehen beide in der Initiierungsphase und unterscheiden sich in der Zahl der Eingabetoken, oder befindet sich die eine in der Initiierungs- und die andere in der Inkrementphase, decken sich die Formen der Attention-Tensoren nicht. Batching wird deshalb nicht auf alle Operationen angewendet, sondern auf ausgewählte. Orca berichtet bei gleichem Latenzniveau von einem 36,9-fachen Durchsatz gegenüber NVIDIA FasterTransformer auf GPT-3 175B: eine Messung aus dem Jahr 2022, gegen die Vergleichsbasis jener Zeit.
Die Begriffe werden nicht in jedem Dokument gleich abgegrenzt. Die Server-Dokumentation von llama.cpp definiert das Flag --cont-batching als „continuous batching (a.k.a dynamic batching)“ und aktiviert es standardmässig. Die begriffliche Trennung oben gehört zur Terminologie von vLLM und SGLang.
PagedAttention: den KV-Cache in Seiten aufteilen
Sinkt das Scheduling auf Iterationsgranularität, wird die Speicherverwaltung zum entscheidenden Faktor. Der Grund ist das Verhalten des KV-Caches: Er ist für jede Anfrage gross, und er wächst und schrumpft, während die Generierung fortschreitet. Die PagedAttention-Arbeit sagt es direkt — dieses Verhalten bestimmt die Batch-Grösse (siehe Efficient Memory Management for Large Language Model Serving with PagedAttention, arXiv:2309.06180, SOSP ’23).
Was die Arbeit misst, ist ein Anteil: Nur 20,4 % bis 38,2 % eines vorab reservierten zusammenhängenden Speicherblocks halten tatsächliche Token-Zustände; bei vLLM sind es in derselben Messung 96,3 %. Der Rest entfällt auf Slots, die für künftige Token reserviert sind, und auf Platz, der durch die Reservierung nach der maximalen Sequenzlänge entsteht.
Die Antwort kommt aus den Betriebssystemen. PagedAttention teilt den KV-Cache einer Anfrage in Blöcke auf, von denen jeder die Key- und Value-Vektoren einer festen Zahl von Token hält, und verlangt nicht, dass diese Blöcke im physischen Speicher nebeneinanderliegen. Die Analogie der Arbeit trägt diesen Abschnitt: „one can think of blocks as pages, tokens as bytes, and requests as processes“.
Die Verwaltungsstruktur folgt daraus. Jeder Eintrag der Blocktabelle hält eine physische Blocknummer und die Zahl der belegten Slots in diesem Block; der Cache einer Anfrage ist eine Folge logischer Blöcke, die von links nach rechts gefüllt wird, und ein neuer physischer Block wird erst reserviert, wenn die vorherigen voll sind. Jeder physische Block trägt einen Referenzzähler, und Copy-on-Write greift auf Blockgranularität: Teilen sich zwei Ausgaben denselben Prompt, wird der Prompt-Zustand in einer einzigen Kopie gehalten, und ein Schreibzugriff reserviert einen neuen Block.
Was das Teilen einbringt, ist gemessen: Bei Beam Search ergibt die gemeinsame Nutzung von Blöcken Speichereinsparungen von bis zu 55 %. Die Arbeit berichtet bei gleichem Latenzniveau von einem 2- bis 4-fachen Durchsatz gegenüber FasterTransformer und Orca — eine Messung aus dem Jahr 2023 gegen die Vergleichsbasen jener Zeit.
RadixAttention und Prefix-Cache: gemeinsamen Kontext wiederverwenden
Der dritte Granularitätswechsel liegt auf der Seite der Wiederverwendung. PagedAttention hat das Teilen innerhalb einer einzelnen Anfrage günstig gemacht; SGLang zielt auf das Teilen zwischen Anfragen (siehe SGLang: Efficient Execution of Structured Language Model Programs, arXiv:2312.07104).
Was RadixAttention auszeichnet, ist schlicht: Nach dem Ende der Generierung wird der Cache nicht verworfen, sondern in einem Radix-Baum gehalten. Ein Radix-Baum ist eine platzsparende Form des Präfixbaums, dessen Kanten mit Folgen variabler Länge statt mit einzelnen Elementen beschriftet werden können. Der Baum verwaltet die Zuordnung zwischen Tokenfolgen und den zugehörigen KV-Cache-Tensoren; zwischengespeichert werden sowohl Prompts als auch Generierungen.
Das ist eine Schicht, keine Alternative. Die Arbeit schreibt ausdrücklich, dass RadixAttention mit Continuous Batching, Paged Attention und Tensor-Parallelität kompatibel ist und dass der zusätzliche Speicher- und Zeitbedarf ohne Cache-Treffer vernachlässigbar bleibt.
Die Freigabe folgt einer LRU-Strategie und beginnt bei den Blättern: Das am längsten nicht genutzte Blatt geht zuerst, gemeinsame Vorgänger bleiben wiederverwendbar, bis sie selbst zu Blättern werden. Ein Cache-Pool fester Grösse wird nicht vorab reserviert; zwischengespeicherte Token und laufende Anfragen teilen sich denselben Speicherpool.
Auch das Scheduling ist cache-bewusst: Anfragen werden nach der Länge des übereinstimmenden Präfixes sortiert. Theorem 3.1 besagt, dass eine Reihenfolge, die das längste gemeinsame Präfix zuerst bedient, einer Tiefensuche im Radix-Baum entspricht und die optimale Trefferquote liefert, sofern die Cache-Grösse nicht kleiner ist als die maximale Anfragelänge.
Wo sich das auszahlt, ist konkret: Few-Shot-Prompts, Chat-Verläufe über mehrere Runden und der gemeinsame Kontext in einer RAG-Pipeline. Über diese Benchmarks hinweg liegt die Trefferquote zwischen 50 % und 99 %, und cache-bewusstes Scheduling erreicht im Mittel 96 % der optimalen Trefferquote. In einem einmonatigen Einsatz auf Chatbot Arena wurden Trefferquoten von 52,4 % für LLaVA-Next-34B und 74,1 % für Vicuna-33B beobachtet; die durchschnittliche Zeit bis zum ersten Token ging bei Vicuna-33B um den Faktor 1,7 zurück — eine Beobachtung aus dem Jahr 2024, spezifisch für diese Arbeitslast.
Dieselbe Funktion bietet vLLM heute ebenfalls; der Unterschied liegt in der Struktur. In vLLM ist die Blockidentität hashbasiert: Der Block-Hash entsteht aus dem Hash des übergeordneten Blocks und den Token in diesem Block, zwischengespeichert werden nur vollständige Blöcke, und ein Cache-Salt geht in den Hash ein, um Caches in Mehrmandanten-Umgebungen zu trennen. Der voreingestellte Hash-Algorithmus ist seit v0.11 sha256, und Prefix-Caching ist standardmässig aktiv. Auf der Seite von SGLang entsteht dieselbe Funktion aus einem Radix-Baum mit LRU-Freigabe der Blätter — gleiche Funktion, andere Datenstruktur.
GGUF und die einzelne Binärdatei: Paketierung und Betriebsmodell
Die vierte Achse löst sich von Scheduling und Speicher: die Paketierung. GGUF ist ein Dateiformat, das Modelle für die Inferenz mit GGML und GGML-basierten Ausführungsumgebungen speichert. Die Entwurfsziele stehen in der Spezifikation der Reihe nach: Auslieferung in einer einzigen Datei — Dateien lassen sich einfach verteilen und laden und benötigen keine externen Dateien; Erweiterbarkeit; mmap-Kompatibilität; einfache Handhabung; und vollständige Information — alles, was zum Laden eines Modells nötig ist, steht in der Datei.
Dieses letzte Ziel ist nicht abstrakt. Die allgemeinen Metadaten-Schlüssel sind als general.architecture, general.quantization_version, general.file_type und general.alignment definiert. Der Tokenizer reist vollständig in der Datei mit: Vokabular, Merge-Regeln, Token-Typen und die IDs der Sondertoken. Der auffälligste Schlüssel ist tokenizer.chat_template — eine Jinja-Vorlage, die das vom Modell erwartete Eingabeformat beschreibt und in derselben Datei liegt wie die Gewichte.
In einer On-Premise-Installation sind zwei verschiedene „einzelne Dateien“ auseinanderzuhalten. Die erste ist die einzelne GGUF-Datei des Modells. Die zweite ist die Ausführungsumgebung selbst: Der erste Punkt der Funktionsliste von llama.cpp ist eine reine C/C++-Implementierung ohne Abhängigkeiten, und die Einheit der Verteilung ist eine herunterladbare Binärdatei statt eines Container-Stacks.
Die Paketierungseinheit von Ollama ist das Modelfile, in der Dokumentation als Bauplan für das Erstellen und Teilen eines Modells beschrieben. Seine Anweisungen sind FROM, PARAMETER, TEMPLATE, SYSTEM, ADAPTER, LICENSE und MESSAGE; FROM nimmt einen vorhandenen Modellnamen, ein Safetensors-Verzeichnis oder den Pfad zu einer GGUF-Datei entgegen. Die Quantisierung ist an den Erstellungsschritt gebunden, in der dokumentierten Form ollama create --quantize q4_K_M; geteilt wird das Modell danach mit ollama push und auf der Gegenseite mit ollama run ausgeführt. Was eine Quantisierungsstufe auf der Seite von Qualität und Speicher bedeutet, gehört zur Modellauswahl; hier geht es um die Paketierung selbst.
Wo das Ökosystem zusammenläuft: das TGI-Repository geht ins Archiv
Das jüngste Signal dafür, warum diese vier Namen zusammen genannt werden, kommt von einem Projekt, das geschlossen wurde. Das Repository Text Generation Inference von Hugging Face wurde am 21. März 2026 archiviert; das Datum steht im Banner der Repository-Seite. Die README trägt einen Hinweis auf den Wartungsmodus.
Wesentlich ist der zweite Satz eben dieses Hinweises. Er hält fest, dass TGI die Bewegung angestossen hat, optimierte Inferenz-Engines auf den Modellarchitekturen von transformers aufzusetzen, und verweist die Leserschaft namentlich auf vLLM, SGLang sowie untereinander kompatible, lokal laufende Engines wie llama.cpp oder MLX. Drei der vier Schichten dieses Beitrags werden damit im Text des Projekts selbst genannt, das nun im Archiv liegt.
Die Chronologie ist kurz: Die letzte getaggte Version war v3.3.7 vom 19. Dezember 2025. Die Funktionsliste des Repositorys zeigt zugleich, was zu diesem Datum als Standard galt — Tensor-Parallelität über mehrere GPUs, Continuous Batching eingehender Anfragen, eine Messages API, die zur OpenAI Chat Completion API kompatibel ist, und Inferenzcode, der Flash Attention und Paged Attention nutzt.
Welche Schicht in einer On-Premise-Installation zu welchem Szenario passt
Das konkreteste Zahlenpaar, das diese Schichten nach ihrer Betriebshaltung trennt, steht in der Dokumentation von Ollama. OLLAMA_NUM_PARALLEL ist die Zahl gleichzeitiger Anfragen je Modell und steht standardmässig auf 1; OLLAMA_MAX_LOADED_MODELS ist die Zahl der gleichzeitig geladenen Modelle und steht standardmässig auf 3 je GPU. Diese Voreinstellungen beschreiben eine Haltung: wenig Nebenläufigkeit, viele Modelle.
Die Voreinstellungen von vLLM und SGLang beschreiben das Gegenteil: ein Modell, hohe Nebenläufigkeit. vLLM plant nach dem First-Come-First-Served-Prinzip; da auf die Blöcke einer Sequenz gemeinsam zugegriffen wird, erfolgt die Freigabe nach dem Alles-oder-nichts-Prinzip. Tensor-Parallelität im Stil von Megatron-LM wird unterstützt, und da jeder Modell-Shard dieselbe Menge an Eingabetoken verarbeitet, hält der zentrale Scheduler nur einen einzigen KV-Cache-Manager und eine einzige Zuordnung von logischen zu physischen Blöcken.
SGLang beschreibt sich selbst als Serving-Framework, das von einer einzelnen GPU bis zu grossen verteilten Clustern skaliert. Die Voreinstellungen in seinen Server-Argumenten bestätigen diese Haltung: Radix-Cache aktiv, lru als Verdrängungsstrategie, Seitengrösse 1, Tensor-Parallelitätsgrad 1.
Die Haltung von llama.cpp ist der minimale Aufbau: eine Backend-Liste, die von BLAS über CUDA bis zu Metal und Vulkan reicht, und hybride CPU+GPU-Inferenz, die Modelle jenseits der gesamten VRAM-Kapazität teilweise beschleunigt. Auf der Seite von Ollama liegen die Modellverzeichnisse an dokumentierten Orten und lassen sich über die Umgebungsvariable OLLAMA_MODELS verschieben; wo Datenlokalität eine schriftlich festgehaltene Anforderung ist, gehört auch dieser Pfad in das Installationsdokument.
Die API-Oberfläche standardisiert den Zugriff auf das Modell; die Schicht, die den Zugriff des Modells auf die Unternehmenssysteme standardisiert, ist eine andere und wird im Beitrag „Was ein MCP-Server ist und was er nicht ist“ behandelt.
Das architektonische Gegenstück zur Frage, wer in einer gemeinsam genutzten Installation welchen Kontext sehen darf, arbeitet der Beitrag „Zugriffskontrolle in RAG: wer welche Inhalte sieht“ aus.
Der Versionstakt ist hoch. Stand 8. August 2026 lautet die getaggte Version von vLLM v0.26.0 (27. Juli 2026), die von SGLang v0.5.17 (8. August 2026). Die Flag-Namen und Voreinstellungen in diesem Beitrag gehören zum selben Datum; gleichen Sie sie vor der Installation mit der Dokumentation Ihrer eigenen Version ab.
Quellen
- Orca: A Distributed Serving System for Transformer-Based Generative Models — USENIX OSDI ’22, S. 521-538 (Scheduling auf Iterationsebene, selektives Batching, die Messung des 36,9-fachen Durchsatzes)
- Efficient Memory Management for Large Language Model Serving with PagedAttention — arXiv:2309.06180, SOSP ’23 (Blocktabelle, Copy-on-Write, Anteil der Token-Zustände)
- SGLang: Efficient Execution of Structured Language Model Programs — arXiv:2312.07104 (RadixAttention, cache-bewusstes Scheduling, Theorem 3.1)
- NVIDIA Triton Inference Server Dokumentation — Dynamic Batcher (max_queue_delay_microseconds)
- vLLM — GitHub-Repository, README.md und vllm/config/cache.py (Funktionsliste, Voreinstellung für Prefix-Caching)
- vLLM Dokumentation — Automatic Prefix Caching, Structured Outputs, OpenAI-Compatible Server, Parallelism and Scaling
- SGLang — GitHub-Repository und Dokumentation, Server Arguments (Voreinstellung Radix-Cache, lru, Seitengrösse 1)
- Text Generation Inference — GitHub-Repository (archiviert am 21. März 2026; letzte Version v3.3.7)
- GGUF Specification — ggml-org/ggml, docs/gguf.md (Entwurfsziele, tokenizer.chat_template)
- llama.cpp — GitHub-Repository und tools/server/README.md (Implementierung ohne Abhängigkeiten, Backend-Tabelle)
- Ollama Dokumentation — Modelfile Reference, Import a model, OpenAI compatibility und FAQ (Voreinstellungen zur Nebenläufigkeit, Modellverzeichnisse)
- Ollama’s new engine for multimodal models — Ollama Blog, 15. Mai 2025
- GitHub Releases — vllm-project/vllm v0.26.0 und sgl-project/sglang v0.5.17