Wer wie ich seine Modelle lokal und souverän betreibt, kennt das Problem: Ich will wissen, wie schnell ein Modell auf meiner Hardware wirklich ist und zwar nicht als theoretische Zahl, sondern so, wie es bei mir als Endnutzer ankommt. Genau dafür gibt es in der llama.cpp-Welt das beliebte Tool llama-bench. Das Problem: Es funktioniert ausschließlich mit llama.cpp. Sobald ich vLLM, SGLang oder einen anderen Inference-Server im Spiel habe, stehe ich ohne vergleichbares Werkzeug da.
In diesem Beitrag stelle ich euch llama-benchy vor. Es ist ein Tool, das genau diese Lücke schließt: Das tolle ist ich bekomme jetzt eine Messungen bei unterschiedlichen Kontext-Tiefen, aber für jeden OpenAI-kompatiblen Endpoint. Egal, ob bei euch Ollama, vLLM oder llama.cpp läuft.
Llama-benchy findet ihr hier auf GitHub: https://github.com/eugr/llama-benchy
Warum die bestehenden Tools nicht reichen
Bevor wir loslegen, lohnt sich ein Blick auf das Motivationsproblem, denn das erklärt sehr gut, was llama-benchy besser macht.
llama-bench ist großartig, hat aber zwei Einschränkungen: Es ist an llama.cpp gebunden, und es misst direkt über die C++-Engine. Diese Messung ist damit nicht unbedingt repräsentativ für das, was ihr als Anwender über die API tatsächlich erlebt.
vLLM bringt zwar ein eigenes, mächtiges Benchmark-Tool mit, das auch gegen andere Engines läuft aber im Detail gibt es Stolperfallen (Quelle: https://github.com/eugr/llama-benchy#motivation):
- Prompt-Processing-Geschwindigkeiten bei verschiedenen Kontext-Längen sauber zu messen, ist knifflig bis unmöglich.
vllm bench sweep servewiederholt denselben Prompt über mehrere Läufe, was beim llama-server direkt den Prefix-Cache trifft. Ergebnis: unrealistisch niedrige TTFT-Werte und absurd hohe Prompt-Processing-Speeds. - Die TTFT-Messung misst nicht die Zeit bis zum ersten nutzbaren Token, sondern bis zum allerersten Daten-Chunk vom Server – und der enthält im
/v1/chat/completions-Modus oft noch gar kein generiertes Token. - Nur das Random-Dataset erlaubt eine frei wählbare Token-Anzahl. Eine zufällig generierte Token-Sequenz lässt sich aber nicht sinnvoll für Speculative Decoding bzw. MTP (Multi-Token Prediction) heranziehen.
Der Autor von llama-benchy schreibt, dass er Anfang Januar 2026 schlicht kein Tool gefunden hat, das llama-bench-Stil-Messungen bei unterschiedlichen Kontext-Tiefen für beliebige OpenAI-kompatible Endpoints liefert. Also hat er es selbst gebaut.
Was llama-benchy kann
Die Feature-Liste ist genau auf die Schwachstellen oben zugeschnitten:
- Misst Prompt Processing (pp) und Token Generation (tg) bei verschiedenen Kontext-Tiefen.
- Trennt optional das Vorbefüllen des Kontexts vom eigentlichen Prompt-Processing über bereits gecachtem Kontext (Prefix-Caching-Messung).
- Liefert TTFR (Time To First Response), est_ppt (geschätzte Prompt-Processing-Zeit) und e2e_ttft (End-to-End Time To First Token).
- Konfigurierbare Prompt-Länge (
--pp), Generierungslänge (--tg) und Kontext-Tiefe (--depth). - Mehrere Durchläufe (
--runs) mit Mittelwert ± Standardabweichung. - Nutzt HuggingFace-Tokenizer für exakte Token-Zählung.
- Behandelt MTP-Chunks korrekt.
- Lädt ein Buch von Project Gutenberg als Textquelle, damit Spec-Decoding/MTP-Modelle realistisch gemessen werden (Standard: Sherlock Holmes).
- Unterstützt nebenläufige Anfragen (
--concurrency), um den Durchsatz unter Last zu messen. - Speichert Ergebnisse als Markdown, JSON oder CSV.
- Erkennt den HuggingFace-Modellnamen automatisch über den
/models-Endpoint, wenn--modelnicht gesetzt ist.
Eine aktuelle Einschränkung solltet ihr kennen: Es wird ausschließlich gegen den /v1/chat/completions-Endpoint gemessen.
Die Installation mit uv
Empfohlen wird die Installation über uv. Das Schöne daran: Ihr müsst nichts dauerhaft installieren. Mit uvx startet ihr die Release-Version direkt aus PyPI:
Befehl: uvx llama-benchy --base-url <ENDPOINT_URL> --model <MODEL_NAME>
Wer lieber die aktuelle Version vom main-Branch testet für den Befehl wie folgt aus:
Befehl: uvx --from git+https://github.com/eugr/llama-benchy llama-benchy --base-url <ENDPOINT_URL> --model <MODEL_NAME>
Alternativ lässt sich das Tool natürlich auch klassisch in ein virtuelles Environment (uv venv + uv pip install -e .) oder ins System (uv pip install -U llama-benchy) installieren.
Die erste Messung
Nach dem ihr llama-benchy bei euch eingerichtet habe hier ein typischer Aufruf gegen einen lokalen Endpoint der wie folgt aussieht:
Befehl: llama-benchy --base-url http://spark:8888/v1 --model openai/gpt-oss-120b --depth 0 4096 8192 16384 32768 --latency-mode generation
Heraus kommt eine Tabelle im vertrauten llama-bench-Look. Diese gibt pro Kontext-Tiefe jeweils eine pp– und eine tg-Zeile, inklusive Standardabweichung. Genau das, was ich für einen seriösen Vergleich brauche: Ich sehe auf einen Blick, wie die Prefill- und Decode-Geschwindigkeit mit wachsendem Kontext einbrechen.
Mein Tipp aus der README, den ich nur unterstreichen kann: Nutzt den „generation“-Latency-Modus. Damit kommen die Prompt-Processing-Werte den realen Zahlen am nächsten. Das gilt besonders bei kürzeren Prompts.
llama-benchy mit Ollama betreiben
Ich betreibe meine Modelle gerne lokal mit Ollama, und das lässt sich völlig problemlos benchmarken. Ollama stellt einen OpenAI-kompatiblen Endpoint unter /v1 bereit. Ihr gebt also einfach die Adresse eures Ollama-Servers als Base-URL an. Läuft Ollama lokal, ist das http://localhost:11434/v1; bei mir läuft die Inferenz auf einem dedizierten Rechner, dann lautet die Base-URL z. B. http://192.168.2.57:11434/v1.
Befehl: uvx llama-benchy --base-url http://localhost:11434/v1 --model qwen3.6:27b --tokenizer Qwen/Qwen3.6-27B --depth 0 4096 8192 --latency-mode generation
Der entscheidende Stolperstein: Der Parameter --tokenizer greift standardmäßig auf den Wert von --model zurück. Bei Ollama ist --model aber der Ollama-Tag (z. B. qwen3.6:27b) und das ist kein gültiger HuggingFace-Tokenizer-Name. llama-benchy würde dann versuchen, ein HF-Repo mit genau diesem Namen zu laden, und scheitern. Deshalb müsst ihr bei Ollama-Modellen --tokenizer immer explizit auf das passende HF-Repo setzen, damit die Token-Zählung stimmt.
Gut zu wissen: Der Tokenizer ist über alle Quantisierungen eines Modells hinweg identisch. Ob ihr BF16, FP8 oder eine GGUF-Quantisierung fahrt, spielt für den Tokenizer keine Rolle. Was ihr machen müsst ist immer auf das offizielle Basis-Repo des Herstellers zu verweisen.
Für meine vier aktuell meistgenutzten Modelle sehen die passenden Tokenizer-IDs so aus:
| Ollama-Tag | HF-Tokenizer-ID (für --tokenizer) |
|---|---|
nemotron3:33b-bf16 |
nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-BF16 |
qwen3.6:27b |
Qwen/Qwen3.6-27B |
qwen3.6:35b-a3b-bf16 |
Qwen/Qwen3.6-35B-A3B |
nemotron3:33b |
nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-BF16 |
Wie ihr seht, zeigen nemotron3:33b und nemotron3:33b-bf16 auf dasselbe HF-Repo – es ist dasselbe Modell (NVIDIA Nemotron 3 Nano Omni), nur einmal als Q4_K_M und einmal als BF16. Genau das ist der oben erwähnte Punkt: Die Quantisierung ändert nichts am Tokenizer.
Mein Ergebnis auf zwei NVIDIA RTX A6000
Ich habe den Benchmark auf meinem Inferenz-Server mit zwei NVIDIA RTX A6000 laufen lassen. Das Modell qwen3.6:35b-a3b-bf16 liegt dabei unquantisiert in BF16 vor.
Befehl: uvx llama-benchy --base-url http://192.168.2.57:11434/v1 --model qwen3.6:35b-a3b-bf16 --tokenizer Qwen/Qwen3.6-35B-A3B --depth 0 4096 8192 --latency-mode generation
| test | t/s | peak t/s | ttfr (ms) | est_ppt (ms) | e2e_ttft (ms) |
|---|---|---|---|---|---|
| pp2048 | 1530.59 ± 21.06 | — | 1618.61 ± 18.72 | 1338.74 ± 18.72 | 1618.61 ± 18.72 |
| tg32 | 57.18 ± 0.56 | 59.05 ± 0.58 | — | — | — |
| pp2048 @ d4096 | 1490.79 ± 2.93 | — | 4402.98 ± 8.73 | 4123.11 ± 8.73 | 4402.98 ± 8.73 |
| tg32 @ d4096 | 56.98 ± 0.43 | 58.85 ± 0.44 | — | — | — |
| pp2048 @ d8192 | 1469.24 ± 2.98 | — | 7249.49 ± 14.70 | 6969.62 ± 14.70 | 7249.49 ± 14.70 |
| tg32 @ d8192 | 56.96 ± 1.88 | 58.83 ± 1.94 | — | — | — |
llama-benchy 0.3.7 · 2026-06-08 · latency mode: generation
Mein Fazit: Wofür taugen diese Werte?
Für den interaktiven Einzelnutzer-Betrieb ist das ein rundum solides Ergebnis. Rund 57 Token/s im Decode liegen weit über meiner Lesegeschwindigkeit. Die Antwort fühlt sich also flüssig an, fast wie Tippen in Echtzeit. Besonders gefällt mir, dass die Decode-Rate über alle Kontext-Tiefen hinweg nahezu konstant bleibt: Ob 0 oder 8k Kontext, ich lande immer bei ~57 t/s. Das macht das Verhalten gut planbar.
Der ehrliche Schwachpunkt ist die Zeit bis zum ersten Token bei langem Kontext. Das Prefill läuft mit stabilen ~1.500 t/s, aber bei 8k Kontext bedeutet das eben ~7 Sekunden Wartezeit, bevor überhaupt etwas zurückkommt. Für einen Chat mit kurzen Prompts ist das irrelevant, für ein RAG-Setup mit großen Kontextfenstern ist es spürbar.
Zwei Stellschrauben sehe ich für mehr Tempo: Erstens läuft das Modell hier unquantisiert in BF16. Da die A6000 (Ampere) kein FP8 in Hardware beherrscht, wäre eine Q4-/Q5- oder AWQ-Quantisierung der naheliegende Hebel. Das würde den Decode deutlich beschleunigen und VRAM freimachen. Genau so ein Vergleich ist mit llama-benchy schnell gemacht. Zweitens verteilt Ollama das Modell über beide Karten als Layer-Split; eine Engine mit echtem Tensor-Parallelismus (z. B. vLLM) könnte hier sowohl Prefill als auch Decode noch heben.
Mein Urteil: Für meinen souveränen Homelab-Betrieb mit einem Nutzer und Modell in voller BF16-Qualität ist das absolut brauchbar. Wer auf Durchsatz unter Last oder schnelle Antworten bei riesigen Kontexten optimiert, sollte Quantisierung und eine andere Inference-Engine gegentesten, und genau dafür ist llama-benchy ja da.
Und jetzt unter Last: vier parallele Clients
Spannend wird es, wenn mehrere Anfragen gleichzeitig auf den Ollama Server treffen. Mit --concurrency 4 schickt llama-benchy vier Clients parallel los. Die Tabelle bekommt dann zwei Durchsatz-Spalten: t/s (total) für den aggregierten Durchsatz aller vier zusammen und t/s (req) für den Schnitt je Einzelanfrage.
Befehl: uvx llama-benchy --base-url http://192.168.2.57:11434/v1 --model qwen3.6:35b-a3b-bf16 --tokenizer Qwen/Qwen3.6-35B-A3B --depth 0 4096 8192 --latency-mode generation --concurrency 4
| test | t/s (total) | t/s (req) | ttfr (ms) | est_ppt (ms) |
|---|---|---|---|---|
| pp2048 (c4) | 1116.50 ± 1.40 | 1561.19 ± 1738.91 | 4495.64 ± 2116.70 | 3289.39 ± 2116.70 |
| tg32 (c4) | 20.06 ± 0.06 | 61.72 ± 0.84 | — | — |
| pp2048 @ d4096 (c4) | 1333.33 ± 2.71 | 879.66 ± 605.50 | 11439.95 ± 5213.15 | 10233.70 ± 5213.15 |
| tg32 @ d4096 (c4) | 8.55 ± 0.02 | 59.96 ± 0.94 | — | — |
| pp2048 @ d8192 (c4) | 1379.59 ± 1.94 | 822.71 ± 518.98 | 18478.59 ± 8353.74 | 17272.34 ± 8353.74 |
| tg32 @ d8192 (c4) | 5.41 ± 0.01 | 60.01 ± 0.87 | — | — |
llama-benchy 0.3.7 · 2026-06-08 · latency mode: generation · peak t/s (req) ~62–64
Mein Fazit zur Concurrency
Auf den ersten Blick sieht t/s (req) hervorragend aus: Jede einzelne Anfrage streamt weiterhin mit rund 60 Token/s, praktisch unverändert gegenüber dem Einzelbetrieb. Der spannende Wert ist aber t/s (total) und der fällt unter Last, statt zu steigen: von ~20 t/s ohne Kontext über 8,5 bis auf 5,4 t/s bei 8k. Vier parallele Nutzer bekommen in Summe also weniger Tokens pro Sekunde als ein einzelner Nutzer (dort waren es 57 t/s).
Der Grund steckt in der TTFT: Sie schnellt auf bis zu ~18 Sekunden bei 8k Kontext hoch, und die enorme Streuung (±8,3 s) verrät, dass manche Anfragen sofort drankommen und andere lange in der Warteschlange stehen. Genau so verhält sich ein Server, der parallele Requests nacheinander abarbeitet, statt sie zu bündeln. Ollama mit seinem llama.cpp-Backend ist eben für den souveränen Einzelnutzer-Betrieb auf der eigenen Hardware gebaut nicht als Mehrnutzer-Serving-Engine.
Mein Takeaway: Für mich als einzelnen Nutzer im Homelab ist alles bestens. Wer aber mehrere Nutzer gleichzeitig bedienen will, braucht eine Engine mit Continuous Batching wie vLLM oder SGLang. Dort würden sich die vier Anfragen den Rechenschritt teilen und der Gesamtdurchsatz läge deutlich über dem Einzelwert. Und genau das ist der Punkt: llama-benchy hat mir diesen Unterschied in einem einzigen Lauf sichtbar gemacht statt es nur zu vermuten, habe ich jetzt die Zahlen.
Wie ihr die passende Tokenizer-ID findet
Für eure eigenen Modelle ist die ID schnell ermittelt:
- Geht auf huggingface.co und sucht nach dem Modellnamen (z. B. „Qwen3.6 27B“ oder „Nemotron 3 Nano Omni“).
- Wählt das offizielle Basis-Repo des Herstellers aus. Das ist erkennbar an der Organisation davor:
nvidia/…,Qwen/…,openai/…oderzai-org/…. Finger weg von GGUF- oder Quant-Forks Dritter (etwaunsloth/…oderlmstudio-community/…); ihr wollt das kanonische Tokenizer-Repo. - Prüft im Reiter „Files and versions“, ob
tokenizer.jsonbzw.tokenizer_config.jsonvorhanden sind. Wenn ja, ist die Repo-ID, also der TeilOrganisation/Modellaus dem Pfad, ist genau das, was ihr bei--tokenizereintragt.
Ein praktischer Trick: In den vLLM- und SGLang-Beispielen auf den HF-Modellkarten taucht die kanonische ID immer als --model-path bzw. --model auf. Das ist exakt die Zeichenkette, die ihr für --tokenizer braucht. Und auf der Modellseite bei ollama.com ist die Upstream-Quelle oft direkt verlinkt.
Die Metriken verstehen
Damit die Tabelle nicht nur hübsch aussieht, sondern ihr sie auch interpretieren könnt, hier die wichtigsten Spalten. Alle Zeiten sind in Millisekunden.
t/s (Tokens pro Sekunde) bedeutet je nach Zeile etwas anderes:
- Bei Prompt Processing:
Gesamte Prompt-Tokens / est_ppt– also die Prefill-Geschwindigkeit. - Bei Token Generation:
(generierte Tokens - 1) / (Zeit des letzten Tokens - Zeit des ersten Tokens)– die reine Decode-Geschwindigkeit, ohne die Latenz des ersten Tokens.
peak t/s gibt es nur bei der Token-Generation: der höchste in einem beliebigen 1-Sekunden-Fenster gemessene Durchsatz während des Laufs.
ttfr (Time To First Response) ist die Rohzeit, bis der Client irgendwelche Stream-Daten vom Server empfängt. Das können auch leere Chunks oder Rollendefinitionen sein. Diese Zahl enthält die Netzwerk-Latenz. Es ist exakt die Messmethode, die auch vllm bench serve als TTFT ausweist.
est_ppt (Estimated Prompt Processing Time) berechnet sich aus TTFR - geschätzte Latenz und schätzt damit die reine Server-seitige Verarbeitungszeit des Prompts.
e2e_ttft (End-to-End Time To First Token) ist Zeit des ersten Content-Tokens - Startzeit. Also die Gesamtzeit, die ich als Nutzer vom Absenden bis zum ersten sichtbaren generierten Token wahrnehme.
Spannend ist der Latency-Mechanismus dahinter. Über --latency-mode schätzt das Tool die Latenz und zieht sie vom ttfr ab, um auf est_ppt zu kommen:
- api (Standard): Zeit, um
/modelsabzurufen – eliminiert nur die Netzwerk-Latenz. - generation: Zeit, um genau 1 Token zu generieren – versucht, Netzwerk und Server-Overhead herauszurechnen.
- none: Latenz wird mit 0 angenommen.
Prefix-Caching realistisch messen
Hier wird es für alle interessant, die mit langem, wiederkehrendem Kontext arbeiten. Das kann etwa bei System-Prompts oder RAG der FAll sein. Mit --enable-prefix-caching (und --depth > 0) führt llama-benchy pro Lauf einen zweistufigen Prozess durch:
- Context Load: Der Kontext wird als System-Message mit leerer User-Message gesendet. Der Server muss ihn verarbeiten und cachen. Reportet als
ctx_pp @ d{depth}. - Inference: Derselbe Kontext plus der eigentliche Prompt als User-Message. Jetzt sollte der Server den gecachten Kontext wiederverwenden. Reportet als
pp{tokens} @ d{depth}.
Genau so sehe ich, wie schnell ein Folge-Prompt bei bereits vorbefülltem Kontext wirklich läuft und das ist genau die Zahl, die im Alltag zählt.
Befehl: llama-benchy --base-url http://spark:8888/v1 --model openai/gpt-oss-120b --depth 0 4096 8192 16384 32768 --latency-mode generation --enable-prefix-caching
Ein Hinweis aus der README: Normalerweise müsst ihr das Prompt-Caching auf dem Server nicht deaktivieren, weil die Wahrscheinlichkeit von Cache-Treffern gering ist. Wenn ihr doch Treffer bekommt, sorgt --no-cache für etwas Rauschen und sendet zusätzlich cache-prompt=false an den Server.
Durchsatz unter Last: Concurrency
Wer einen Server für mehrere parallele Nutzer betreibt, will den Sättigungspunkt kennen. Mit --concurrency 1 2 4 startet llama-benchy mehrere parallele Clients und ergänzt die Tabelle um zwei Spalten:
- t/s (total): der aggregierte Durchsatz aller Clients zusammen.
- t/s (req): der durchschnittliche Durchsatz pro Client.
So findet ihr genau den Punkt, an dem mehr Clients den Gesamtdurchsatz nicht mehr steigern. Das ist extrem praktisch, um die eigene Hardware richtig einzuschätzen.
Übrigens lassen sich --depth, --pp, --tg und --concurrency beliebig kombinieren. Die Benchmarks laufen dann in der Hierarchie depth → pp → tg → concurrency über alle Kombinationen.
Weiterverarbeitung der Ergebnisse
Für eigene Auswertungen oder Visualisierungen exportiert ihr die Daten als JSON oder CSV. --format json liefert die detailliertesten Daten, und mit --save-total-throughput-timeseries bekommt ihr sogar den Gesamtdurchsatz in 1-Sekunden-Intervallen mit ins JSON geschrieben. Damit lässt sich der Verlauf eines Laufs sauber plotten.
Fazit
llama-benchy ist genau das Werkzeug, das mir in meinem Homelab gefehlt hat. Ich kann meine lokal laufenden Modelle endlich mit denselben, vertrauten llama-bench-Metriken vergleichen. Das tolle dabei ist noch das ich unabhängig bin welche Inference-Engine hinter dem API-Endpunkt arbeitet. Dass das Tool die Schwächen bestehender Benchmarks gezielt adressiert (echte TTFT bis zum ersten nutzbaren Token, sauberes Prefix-Caching, realistische Textquelle für MTP), macht es für mich glaubwürdig.
Für mich passt es perfekt in den Gedanken der souveränen KI: Ich messe auf meiner eigenen Hardware, mit meinen eigenen Modellen, mit einem schlanken Open-Source-Tool, das per uvx ohne Installations-Ballast startet. Wenn ihr lokale LLM-Endpoints betreibt und endlich belastbare Zahlen statt Bauchgefühl wollt, kann ich euch llama-benchy klar empfehlen.
Das Projekt findet ihr auf GitHub: github.com/eugr/llama-benchy





Ein toller Guide der leicht zugänglich und verständlich ist. Perfekt für ein kleines Side-Project geeignet. Aktuell half mir noch mein…
Thank you for this great tutorial, could you share n8n workflow and comfyui workflow please?
Hallo Anton, die Meldung besagt das in meinem Beisiel Methoden verwendet werden die veraltet (deprecated) sind. Also müsstest Du die…
Danke für das Tool! Ich habe erst kürzlich angefangen mich mit der Thematik zu beschäftigen und bin für meine Erwartungen…
Hallo, ich habe ihre Anleitung befolgt und bekomme im letzten Schritt leider immer folgende Meldung im Terminal: bash <(wget -qO-…