Einen Modelldienst mit vLLM bereitstellen
Bedient man viele Nutzer gleichzeitig, lautet die Frage, wie man mit derselben Grafikkarte möglichst viele Anfragen verarbeitet. Mit dem kleinen GPT die Wirkung gebündelter Verarbeitung messen, dann erklären, wie vLLM den KV-Cache verwaltet und wie man einen OpenAI-kompatiblen Dienst startet.
- Etwa 35 Minuten
- Niveau: Vertiefung
- Getestet: 2026-09-15 Stapelexperiment auf Apple-M4-CPU; Nutzung von vLLM laut offizieller Dokumentation
Code und Programmausgaben stehen genau so da, wie sie gelaufen sind – Kommentare und Ausgaben sind daher auf Chinesisch.
Die vorige Lektion hat mit Ollama ein Modell auf dem eigenen Rechner ausgeführt; für eine Person ist das bequem. Soll das Modell aber ein Dienst werden, für Hunderte Menschen in der Firma oder für die Nutzer deines Produkts, ist das Problem ein anderes: Dutzende Anfragen kommen gleichzeitig; wie verarbeitet man sie auf ein, zwei Grafikkarten möglichst schnell?
vLLM ist eines der derzeit verbreitetsten Open-Source-Frameworks zum Bereitstellen großer Modelle. Diese Lektion macht zuerst im Experiment das Kernproblem der Serverseite sichtbar und erklärt dann, wie vLLM es löst und wie man es nutzt.
vLLM richtet sich vor allem an Linux-Server mit NVIDIA-Grafikkarten; mein Versuchsrechner (Mac, keine NVIDIA-Grafikkarte) kann es nicht ausführen. Das Experiment dieser Lektion nutzt deshalb das kleine GPT aus Modul 09; im Teil zur Nutzung von vLLM stehen nur die Befehle aus der offiziellen Dokumentation, keine Ausgaben, die ich nicht selbst erzeugt habe.
Ein Gedicht nach dem anderen oder viele auf einmal?
Modul 09, Lektion 6 hat gesagt, dass jeder Generierungsschritt alle Parameter des Modells einmal liest, aber nur ein Zeichen berechnet. Bei großen Modellen geht die Zeit dieses Schritts vor allem für das Lesen der Parameter aus dem Grafikspeicher drauf; die Recheneinheiten warten die meiste Zeit auf Daten.
Was, wenn man mehrere Anfragen zusammenlegt, die Parameter einmal liest und für mehrere Anfragen gleichzeitig je ein Zeichen berechnet?
for batch in [1, 4, 16, 64]:
for _ in range(N_POEMS // batch):
model.generate(torch.full((batch, 1), NEWLINE), N_TOKENS) # batch 首诗同时写
python batch_throughput.py
一共要写 64 首,每首 60 个字,用 KV 缓存
一次写 1 首:总共 1.84 秒,每秒 2085 个字,每一首从开始到写完 0.03 秒
一次写 4 首:总共 0.93 秒,每秒 4110 个字,每一首从开始到写完 0.06 秒
一次写 16 首:总共 0.48 秒,每秒 8053 个字,每一首从开始到写完 0.12 秒
一次写 64 首:总共 0.44 秒,每秒 8706 个字,每一首从开始到写完 0.44 秒
Ebenfalls 64 Gedichte: Eins nach dem anderen dauert 1,84 Sekunden; 16 auf einmal nur 0,48 Sekunden, die Zeichen pro Sekunde (Durchsatz) steigen auf etwa das Vierfache.
Beachte aber die letzte Spalte. Bei 1 Gedicht pro Stapel ist jedes nach 0,03 Sekunden fertig; bei 64 pro Stapel wartet jedes 0,44 Sekunden auf sein Ergebnis. Je größer der Stapel, desto höher die Gesamteffizienz, desto länger aber auch die Wartezeit jeder Anfrage (Latenz).
Außerdem steigt der Durchsatz von 16 auf 64 kaum noch (8053 auf 8706): Die Rechenleistung der CPU ist ausgeschöpft. Grafikkarten haben viel mehr Rechenleistung und profitieren viel stärker von gebündelter Verarbeitung.
Das ist das Kernproblem, das ein Modelldienst lösen muss: innerhalb einer akzeptablen Latenz möglichst viele Anfragen zusammen berechnen.
Zwei Schwierigkeiten echter Dienste
Unser Experiment ist sehr ideal: 64 Gedichte beginnen gleichzeitig, jedes hat 60 Zeichen. Echte Anfragen sind nicht so.
Anfragen sind unterschiedlich lang und kommen jederzeit. Eine Anfrage soll 20 Zeichen schreiben, eine andere 2000. Legt man sie in einen Stapel und bearbeitet den nächsten Stapel erst, wenn die längste fertig ist, sind die kurzen längst fertig und belegen trotzdem ihren Platz; neue Anfragen müssen warten. vLLM löst das mit Continuous Batching: Nach jedem Generierungsschritt wird geprüft, fertige Anfragen verlassen den Stapel sofort, neue kommen sofort hinzu. Die Anfragen im Stapel wechseln ständig, und die Grafikkarte arbeitet immer unter Volllast.
Der KV-Cache braucht viel Platz, und seine Größe ist unbestimmt. Modul 09, Lektion 6 und die vorige Lektion haben es berechnet: Der KV-Cache wächst mit dem Text, und wie viel jede Anfrage am Ende belegt, weiß man vorher nicht. Herkömmlich reserviert man für jede Anfrage einen zusammenhängenden Block Grafikspeicher für die maximale Länge; die meisten Anfragen brauchen längst nicht so viel, viel Grafikspeicher wird verschwendet, und es lassen sich weniger Anfragen gleichzeitig verarbeiten.
PagedAttention
Die zentrale Erfindung von vLLM heißt PagedAttention, aus dem Paper „Efficient Memory Management for Large Language Model Serving with PagedAttention“ von 2023. Sie übernimmt das „Paging“, mit dem Betriebssysteme Speicher verwalten:
传统做法:每个请求预留一整块,按最大长度
请求 A [■■■■□□□□□□□□□□□□] 用了 4 格,空着 12 格
请求 B [■■■■■■□□□□□□□□□□] 用了 6 格,空着 10 格
分页:KV 缓存切成固定大小的小块,用到哪儿分配到哪儿
块池 [A1][B1][A2][B2][B3][C1][空][空] ...
请求 A 的块表:A1 → A2
请求 B 的块表:B1 → B2 → B3
Der KV-Cache jeder Anfrage wird in kleine Blöcke fester Größe geteilt, die nicht zusammenhängend liegen müssen; ist ein Block voll, wird der nächste zugewiesen, und jede Anfrage nutzt nur, was sie tatsächlich braucht. So wird fast nichts verschwendet, und mit demselben Grafikspeicher lassen sich viel mehr Anfragen gleichzeitig bedienen.
Haben mehrere Anfragen denselben Anfang (etwa denselben System-Prompt), können diese Blöcke geteilt werden, statt für jede einzeln gespeichert zu werden. „Eingaben mit Cache-Treffer sind billiger“ aus Modul 06, Lektion 4 beruht auf der Serverseite auf einem ähnlichen Mechanismus.
Laut Abstract des Papers erreichte vLLM bei gleicher Latenz den 2- bis 4-fachen Durchsatz der damaligen anderen Systeme, mit umso größerem Vorsprung, je länger die Texte und je größer das Modell.
Installation und Start
Die folgenden Befehle stammen aus der offiziellen Dokumentation von vLLM (Stand September 2026) und brauchen einen Linux-Rechner mit NVIDIA-Grafikkarte. vLLM unterstützt auch Grafikkarten von AMD und Intel sowie einige CPUs und andere Hardware, die Installation ist aber anders; siehe offizielle Dokumentation.
uv venv --python 3.12 --seed
source .venv/bin/activate
uv pip install vllm --torch-backend=auto
Einen Dienst zu starten braucht nur einen Befehl:
vllm serve Qwen/Qwen2.5-0.5B-Instruct
Standardmäßig steht unter http://localhost:8000 eine OpenAI-kompatible Schnittstelle bereit. Wie bei Ollama in der vorigen Lektion funktioniert der Code aus Teil 1 mit drei geänderten Umgebungsvariablen:
export LLM_BASE_URL=http://localhost:8000/v1
export LLM_API_KEY=你设置的密钥
export LLM_MODEL=Qwen/Qwen2.5-0.5B-Instruct
Soll der Dienst von anderen genutzt werden, unbedingt eine Schlüsselprüfung ergänzen. Beim Start --api-key angeben oder die Umgebungsvariable VLLM_API_KEY setzen, dann prüft der Server den Schlüssel im Header der Anfrage. Modul 06, Lektion 6 hat gezeigt: Eine Schnittstelle ohne jede Beschränkung im Netz heißt, für andere zu bezahlen.
Mehrere LoRA gleichzeitig
Lektionen 2 und 3 nannten einen Vorteil von LoRA: ein Basismodell plus viele kleine Adapter. vLLM kann in einem Dienst mehrere LoRA gleichzeitig laden, und jede Anfrage gibt an, welches sie nutzt:
vllm serve Qwen/Qwen2.5-0.5B-Instruct \
--enable-lora \
--lora-modules repobot=./.cache/repobot-lora
Gibt man bei der Anfrage als Modellnamen den LoRA-Namen repobot an, wird der in Lektion 3 feingetunte Adapter verwendet; mit dem Namen des ursprünglichen Modells das ursprüngliche Modell. Mehrere Kunden haben jeweils eigene LoRA, belegen aber nur den Grafikspeicher eines Basismodells.
Selbst bereitstellen oder API aufrufen?
An diesem Punkt kannst du Modelle selbst bereitstellen. Ob du es tun solltest, ist eine andere Frage.
Selbst bereitstellen heißt: Grafikkarten kaufen oder mieten, die auch im Leerlauf Geld kosten; Skalierung, Überwachung, Ausfälle und Updates selbst erledigen; und die nutzbaren Modelle sind durch das begrenzt, was auf deine Grafikkarte passt.
Eine API kostet nach Verbrauch, ohne Leerlaufkosten, und man kann die besten Modelle nutzen. Modul 01, Lektion 4 hat gerechnet: APIs wie DeepSeek sind schon sehr günstig.
Allgemein lohnt sich eigener Betrieb in diesen Fällen: Daten dürfen die eigenen Server nicht verlassen; die Aufrufmenge ist groß und stabil, sodass eigener Betrieb insgesamt billiger ist; man braucht ein eigenes feingetuntes Modell. Vor der Entscheidung sollte man nach der Methode aus Modul 06, Lektion 4 die Kosten beider Varianten tatsächlich durchrechnen.
Übungen
- Füg in
batch_throughput.pyStapelgrößen 8 und 32 hinzu und finde die Stapelgröße, ab der der Durchsatz auf deinem Rechner nicht mehr deutlich steigt. - Ändere das Experiment so, dass die Gedichte in einem Stapel unterschiedlich lang sind (etwa die Hälfte 20 Zeichen, die andere Hälfte 120) und der ganze Stapel erst endet, wenn das längste fertig ist. Um wie viel sinkt der Durchsatz gegenüber lauter 60-Zeichen-Gedichten? Genau dieses Problem löst Continuous Batching.
- Hast du Zugang zu einem Rechner mit NVIDIA-Grafikkarte (etwa einem Cloudserver), starte vLLM mit den Befehlen dieser Lektion und ruf es mit RepoBot v4 aus Modul 06 auf.
Selbsttest
1. Warum steigt der Durchsatz, wenn man mehrere Anfragen zusammen verarbeitet? Was ist der Preis?
Jeder Generierungsschritt liest die Parameter des Modells einmal, und bei großen Modellen geht die meiste Zeit fürs Lesen drauf. Verarbeitet man mehrere Anfragen zusammen, liefert ein einmaliges Lesen der Parameter für jede Anfrage ein Zeichen, und die Recheneinheiten werden voll genutzt. Der Preis ist eine längere Latenz jeder Anfrage: Sie wird mit anderen zusammen berechnet, und ab einer gewissen Stapelgröße ist die Rechenleistung ausgeschöpft und der Durchsatz steigt nicht mehr.
2. Welches Problem löst Continuous Batching?
Echte Anfragen sind unterschiedlich lang und kommen jederzeit. Muss ein Stapel warten, bis die längste Anfrage fertig ist, bevor der nächste kommt, belegen fertige kurze Anfragen weiter ihren Platz und neue müssen warten. Continuous Batching entfernt bei jedem Schritt fertige Anfragen und nimmt neue auf, sodass die Grafikkarte immer unter Volllast arbeitet.
3. Wie spart PagedAttention Grafikspeicher?
Es teilt den KV-Cache jeder Anfrage in kleine Blöcke fester Größe, die nach Bedarf zugewiesen werden, statt für die maximale Länge einen zusammenhängenden Block Grafikspeicher zu reservieren; so wird fast nichts verschwendet. Blöcke mit gleichem Präfix lassen sich zwischen Anfragen teilen. Den gesparten Grafikspeicher kann man nutzen, um mehr Anfragen gleichzeitig zu verarbeiten.
Fragen und Diskussion
Hängst du in dieser Lektion fest? Frag hier. Und wenn du die Frage von jemandem beantworten kannst, tu es gern.
Eine Frage bringt 3 Punkte, eine Antwort 6. Beiträge erscheinen nach der Prüfung.
Diskussion wird geladen…