Hetzner verschenkt Inferenz. Vier Open-Weight-Modelle hinter einer OpenAI-kompatiblen API unter inference.hetzner.com, kostenlos solange sie experimentell ist, ohne SLA, und einer Ankündigung, in der steht: „No promises this becomes a permanent product.“
Ich habe jedem Modell denselben 38-Token-Prompt gegeben und 512 Tokens, um ihn zu beantworten. Drei von vier kamen ganz ohne Antwort zurück.
Wohin die Tokens gingen
Qwen 3.6, DeepSeek V4 und GLM 5.2 erzeugten jeweils exakt 512 Completion-Tokens und null sichtbare Wörter. Das Budget floss vollständig ins Nachdenken. Die API sagt das ehrlich: finish_reason: length und ein paar tausend Zeichen Reasoning. Aber message.content kommt als null zurück, und ein Client, der nur dieses Feld liest, sieht eine erfolgreiche Anfrage, die nichts gesagt hat.
Nur Kimi K2.7 hat geantwortet, mit 266 Wörtern und vergleichsweise sparsamen 568 Zeichen Nachdenken.
Das passiert, wenn Reasoning-Modelle auf ein Token-Budget treffen, das für Modelle ohne Reasoning gewählt wurde. Es ist das Erste, was man wissen sollte, bevor man das irgendwo einbaut. Ein Token-Budget ist ein Budget in Tokens, nicht in Wörtern, und was ein Token kostet, ist vorher nicht offensichtlich.
Zwei Stellen, an denen ein Standard-Client bricht
„OpenAI-kompatibel“ ist großzügig formuliert.
Die Stream-Frames kommen als data:{...}, ohne Leerzeichen nach dem Doppelpunkt. Server-Sent Events erlauben das, und das OpenAI-SDK kommt damit klar. Ein selbst geschriebener Parser, der auf das dokumentierte data: splittet, liest null Tokens und meldet eine funktionierende Anfrage mit leerer Ausgabe. Genau das tat mein erster Versuch, und ein paar Minuten lang hielt ich die Modelle für das Problem. Dasselbe Muster wie bei dem Reviewer, der einen erfundenen Befund einreichte: Das Werkzeug meldete Erfolg, und der einzige Weg, es besser zu wissen, war, seine Ausgabe gegen etwas außerhalb zu prüfen.
Das Nachdenken kommt auf delta.reasoning. vLLM und das OpenAI-SDK verwenden reasoning_content. Wer nur dieses Feld zählt, misst bei drei dieser vier Modelle null Durchsatz, während die Tokens erzeugt, gegen das Rate-Limit gerechnet und vom eigenen Parser weggeworfen werden.
Der Durchsatz ist in Ordnung. Das Warten nicht.
Sobald Tokens fließen, sind die Zahlen für einen kostenlosen Dienst ordentlich: Qwen 3.6 hielt 41 Tokens pro Sekunde, DeepSeek V4 38, Kimi K2.7 30.
Davor fällt es auseinander. Die mediane Zeit bis zum ersten Token reichte von 2,1 Sekunden bei Qwen bis 26,8 Sekunden bei GLM 5.2. Die fünf Läufe von GLM, in der Reihenfolge, in der sie passierten:
- 122,94 s · 172,65 s · 26,77 s · 18,09 s · 11,71 s
Ein Modell wird nicht innerhalb von fünf Minuten fünfzehnmal schneller. Diese Spanne ist eine Warteschlange, die sich leert. Das heißt: Welche Zahl ich für GLM nennen kann, hängt allein davon ab, wann ich gefragt habe. Verursacht habe ich sie nicht. Der gesamte Benchmark verbrauchte 17.920 Output-Tokens gegen ein Limit von 200.000 pro Minute, kein Request kam als 429 zurück, und GLM lief als letztes. Wäre ich die Verstopfung gewesen, wäre es langsamer geworden, nicht schneller. Die anderen drei sind stabiler, die fünf Läufe von Qwen lagen zwischen 1,68 und 2,20 Sekunden, aber derselbe Vorbehalt gilt für sie in kleinerem Maßstab.
Beim Batchen ist sie stark
Acht 256-Token-Anfragen gleichzeitig an Qwen 3.6 bremsten keine einzelne aus: Der Durchsatz pro Anfrage blieb bei rund 36 Tokens pro Sekunde gegenüber 34 bei einer allein, während der Gesamtdurchsatz von 15 auf 217 Tokens pro Sekunde stieg. So verhält sich ein Backend, das fürs Batchen gebaut ist, und genau dafür ist diese API attraktiv: Offline-Arbeit, Massenklassifikation, alles, wo niemand auf einen blinkenden Cursor schaut.
DeepSeek V4 war unter derselben Behandlung deutlich unruhiger und fiel bei acht parallelen Anfragen auf 15 Tokens pro Sekunde, um bei vier wieder zu steigen. Auf fünf Stichproben würde ich darauf kein Argument bauen; ich berichte es, weil das Rauschen bei einem Dienst ohne Kapazitätsgarantie selbst der Befund ist.
Der Teil, den du kopieren willst
Das Repository dieser Seite ist privat, ein Verweis auf eine Datei darin bringt dir also nichts. Hier steht das Stück, das beide Befunde trägt: der Streaming-Parser und die Zeitmessung. Der Rest meines Runners liest Argumente ein und bildet Mediane.
Die Messung, vollständig
const res = await fetch(`${BASE}/chat/completions`, {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
model, messages: [{ role: 'user', content: PROMPT }],
max_tokens: 512, temperature: 0,
stream: true, stream_options: { include_usage: true },
}),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '', first = null, last = null, visible = '', reasoning = '', usage = null;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
// 'data:' ohne Leerzeichen. Wer auf das dokumentierte 'data: ' splittet,
// liest null Tokens und meldet eine erfolgreiche Anfrage ohne Ausgabe.
if (!line.startsWith('data:')) continue;
const payload = line.slice(5).trim();
if (!payload || payload === '[DONE]') continue;
const frame = JSON.parse(payload);
if (frame.usage) usage = frame.usage;
const delta = frame.choices?.[0]?.delta;
if (!delta) continue;
const text = delta.content ?? '';
// delta.reasoning, nicht das reasoning_content von vLLM und OpenAI-SDK.
// Nur delta.content zu zählen misst bei drei der vier Modelle null.
const think = delta.reasoning ?? delta.reasoning_content ?? '';
if (!text && !think) continue;
if (first === null) first = performance.now();
last = performance.now();
visible += text;
reasoning += think;
}
}
// Aktiver Durchsatz ohne die Wartezeit. End-to-end teilt durch die ganze Anfrage.
const activeTps = usage.completion_tokens / ((last - first) / 1000);
Richte es auf jeden OpenAI-kompatiblen Endpunkt. Wenn du es gegen diese API laufen lässt und andere Zahlen bekommst, ist das der Befund und kein Widerspruch — siehe die Vorbehalte weiter unten.
Was das ist und was nicht
- Ein Client, ein Ort, ein Nachmittag. Ein Privatanschluss in Berlin, fünf Läufe pro Modell. Gegen einen Dienst, der ausdrücklich kein SLA hat, auf Hardware, die sich alle teilen, die dieselbe Ankündigung gelesen haben.
- Gemessen ist, was ein Nutzer bekommen hat, nicht was die Hardware kann. Jede Zahl hier wäre an einem anderen Tag eine andere, die von GLM deutlich.
- Übertragbar ist die Methode. Die Messschleife steht oben, vollständig. Sie schreibt sowohl das Roh-JSON als auch das Modul, das diese Figur liest. Ein neuer Durchlauf bewegt also das Diagramm, statt dass ich die Zahlen von Hand hinübertrage.
Würde ich sie nutzen
Für Batch-Arbeit, bei der Latenz egal ist: ja, und gern. Kostenlose Kapazität auf europäischer Hardware, mit der erklärten Absage daran, Anfrageinhalte zu speichern, ist ein wirklich gutes Angebot — und der Durchsatz hält unter Last.
Für alles Interaktive: nicht, solange die Wartezeit auf das erste Token von der Tageszeit abhängt. Und nicht über einen Standard-OpenAI-Client, solange delta.reasoning nicht entweder reasoning_content heißt oder in der Doku klar steht, dass es das nicht tut.
Genau solche Dinge soll eine experimentelle Plattform zutage fördern. Vermutlich ist das der Grund, sie so auszuliefern. Die Ankündigung bittet um genau dieses Feedback.
Next
Die offensichtlich fehlende Messung ist ein größeres Budget. Jede Zahl oben ist von 512 Tokens begrenzt, genau die Randbedingung, die den Hauptbefund erzeugt hat. Bei 4.096 würden die drei denkenden Modelle antworten, und die interessante Frage wäre, wie viel vom Budget das Nachdenken kostet, statt ob es alles auffrisst.
Die zweite ist die Tageszeit. Fünf Läufe an einem Nachmittag können ein langsames Modell nicht von einem ausgelasteten trennen, und die Spanne von GLM 5.2 sagt, dass genau diese Unterscheidung bei mindestens einem davon die ganze Geschichte ist. Dafür bräuchte es dasselbe Skript eine Woche lang im Zeitplan, und das ist ein eigener Post.
Falls du selbst einen Client auf diese API gerichtet hast: Kam beim ersten Versuch sichtbare Ausgabe zurück, oder hast du auch eine Weile geglaubt, die Modelle seien kaputt? Mich interessiert, ob das leere content alle trifft oder ob ich mit den 512 selbst hineingelaufen bin.
Gemessen am 11. August 2026 über einen Privatanschluss in Berlin, fünf gestreamte Läufe pro Modell. Jede Zahl in der Figur stammt aus scripts/bench-inference.mjs und dem daneben eingecheckten Rohbericht. Hetzner erklärt ausdrücklich, dass der Dienst kein SLA hat; an einem anderen Tag wären die Zahlen andere, bei GLM 5.2 deutlich andere.
Baust du an etwas Ähnlichem?
Ich schreibe hier über Setups, die ich selbst benutze. Wenn du an etwas Vergleichbarem arbeitest, würde mich interessieren, wie dein Workflow aussieht.