SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-13

Tokens per seconde meten voor lokale LLM's

Bepaal of het huren van een GPU rendabel is door uw eigen tokens per seconde te meten. Voer een concurrency sweep uit en vergelijk de kosten met per-token API-tarieven.

Waarom tokens per seconde bepalen of een GPU rendabel is

Tokens per seconde is de snelheid waarmee uw server output genereert. Dit getal bepaalt of het huren van een GPU goedkoper is dan betalen per token via een API. Voor een GPU-server betaalt u per uur, ongeacht of deze actief of inactief is. Een gehoste API wordt per token afgerekend. De GPU is daarom alleen rendabel als u gedurende het grootste deel van de betaalde uren een voldoende hoge output-snelheid behaalt.

Dit betekent dat u een eigen meting nodig heeft, in plaats van een cijfer dat u ergens leest. Deze pagina definieert de vier getallen die u moet bijhouden, geeft de commando's om deze te genereren en toont de berekening waarmee u een beslissing neemt.

Waarom een gepubliceerd aantal tokens per seconde niet uw getal is

DigitalOcean publiceerde in juli 2026 doorvoersnelheden voor een enkele NVIDIA H200 die llama3.3-70b-instruct draait in FP8 (8-bit floating point) onder vLLM. Deze cijfers zijn nuttig, maar ze zijn niet van u.

ChartPublished DigitalOcean H200 figures, llama3.3-70b-instruct FP8, July 2026
The data behind this chart
[
  {
    "config": "H200, one stream",
    "tok_s": "47"
  },
  {
    "config": "H100, saturated",
    "tok_s": "236"
  },
  {
    "config": "H200, saturated",
    "tok_s": "2,036"
  },
  {
    "config": "H200, saturated, in+out",
    "tok_s": "4,071.6"
  }
]

Elke rij hierboven is geciteerd van die pagina, en twee daarvan vormen de ondergrens van het bereik dat wordt gegeven; beschouw die twee dus als een minimum. Niets in deze tabel is door ons gemeten.

Begin bij de laatste twee rijen. De koptekst is 4,071.6 tok/s, terwijl de snelheid voor alleen output 2,036 tok/s is. De koptekst telt input- en output-tokens bij elkaar op. Die test gebruikte 1.024 input-tokens tegenover 1.024 output-tokens, dus bijna precies de helft van de koptekst is output. De verdeling is van belang omdat output de helft is waarvoor u betaalt, en het is de langzame helft. Prefill (het lezen van de prompt) verwerkt alle input-tokens in één doorgang. Decode (het schrijven van het antwoord) produceert één token per keer. Een koptekst over totale doorvoer middelt een goedkoop getal met een duur getal.

Kijk nu naar de eerste rij. Dezelfde H200 die één verzoek tegelijk bedient, produceert 47 tok/s, dus het verzadigde cijfer is meer dan veertig keer hoger op identieke hardware. Dat gat bestaat omdat een enkele decode-stap de GPU het grootste deel van de tijd laat wachten op het geheugen, en gelijktijdige verzoeken vullen die inactieve tijd op. De tweede rij, 236 tok/s, is een enkele H100 op hetzelfde model, die wordt tegengehouden door de KV-cache (de key and value cache, het geheugen per verzoek dat een bediend gesprek op de kaart vasthoudt). Een kaart van 80 GB houdt minder gelijktijdige verzoeken vast voor een 70B-model, dus verzadigt deze op een lager niveau.

Verander het model of verander de verhouding tussen input en output en elk getal hierboven verandert. Gepubliceerde cijfers bepalen uw verwachtingen, niet uw budget; dit is dezelfde regel die geldt voor het eerlijk benchmarken van een VPS op schijf en netwerk.

De vier getallen die ertoe doen

  • Time to first token, TTFT. De vertraging tussen het verzenden van een verzoek en het arriveren van het eerste output-token. Dit is de som van de prefill-tijd en de wachtrijtijd. Een gebruiker merkt dit direct op.
  • Output tokens per second, per stream. Hoe snel één antwoord wordt geschreven zodra het is begonnen. Boven ongeveer 20 tok/s is het al sneller dan de meeste mensen kunnen lezen, dus extra snelheid levert hier weinig op.
  • Saturated total output throughput. De som van alle gelijktijdige streams wanneer de server volledig belast is. Dit is het capaciteitsgetal en dit is het getal dat de GPU-kosten rechtvaardigt.
  • p50 and p99 TTFT under concurrency. p50 is het middelste verzoek. p99 is de waarde waar 99 van de 100 verzoeken onder blijven. Wachtrijvorming is altijd als eerste zichtbaar in de p99.

De eerste twee worden beter wanneer de server rustig is. De derde wordt beter wanneer de server druk is. Ze werken elkaar tegen, en dat is de reden waarom geen enkel getal een server volledig beschrijft.

Corrigeer de lengtes van input en output voordat u gaat meten

De doorvoersnelheid is afhankelijk van het type verkeer. Een prompt van 4.000 tokens met een antwoord van 50 tokens is werk dat zwaar leunt op prefill. Een prompt van 200 tokens met een antwoord van 2.000 tokens is werk dat zwaar leunt op decode. Dezelfde server rapporteert zeer verschillende waarden voor tokens per seconde voor deze twee scenario's. Kies daarom één verhouding, noteer deze bij elk getal dat u vastlegt en vergelijk nooit resultaten met verschillende verhoudingen. Een verhouding van 1.024 in tegenover 1.024 uit is een redelijke standaard, omdat verschillende leveranciers hun cijfers op basis van deze verhouding publiceren. Als u uw werkelijke verkeer kent, gebruik dan uw werkelijke verkeer.

Forceer ook de outputlengte. Een model dat na 60 tokens zijn stop-token bereikt, levert een kortere run op die sneller lijkt, omdat TTFT dan een groter aandeel in de totale tijd vormt. De --ignore-eos-vlag in de vLLM-benchmarkclient zorgt ervoor dat elk verzoek exact het gevraagde aantal tokens genereert, zodat twee runs vergelijkbaar blijven. De keuze voor een model heeft meer invloed op deze cijfers dan welke vlag dan ook: het passen van een Qwen 3-model op een enkele VPS GPU behandelt de geheugenkant van die keuze.

Meet eerst één stream

Begin met het eenvoudigste scenario. Dit is een sanity check en vormt het theoretische maximum. Ollama toont zijn eigen timinggegevens.

ollama run llama3.1:8b --verbose "Write 200 words about disk scheduling."

De regel die u moet lezen is eval rate, wat staat voor het aantal gegenereerde tokens per seconde. prompt eval rate is de prefill-snelheid en load duration is de tijd die nodig is om het model in het VRAM te laden. Bij de eerste aanroep na een koude start is load duration groot, waardoor total duration een vertekend beeld geeft. Voer het commando tweemaal uit en lees het tweede resultaat af. Ollama ontlaadt een inactief model standaard na vijf minuten, dus een lange pauze tussen runs brengt u terug naar de situatie van een koude start.

Dezelfde velden zijn beschikbaar via de API, wat eenvoudiger is om te scripten.

curl -s http://127.0.0.1:11434/api/generate -d '{
  "model": "llama3.1:8b",
  "prompt": "Write 200 words about disk scheduling.",
  "stream": false
}' | jq '{eval_count, eval_duration,
         tok_s: (.eval_count / (.eval_duration / 1000000000))}'

eval_duration is uitgedrukt in nanoseconden, dus delen door 1.000.000.000 geeft het aantal seconden. Deze deling is precies wat de API-documentatie van Ollama voorschrijft voor tokens per seconde. Als de server nog niet actief is, behandelt self-hosting an LLM with Ollama on a VPS de installatie en de systemd-unit.

TTFT vereist een streaming-verzoek, en curl kan dit voor u timen.

curl -s -o /dev/null -N \
  -w 'ttfb=%{time_starttransfer}s  total=%{time_total}s\n' \
  http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"Qwen/Qwen3-8B",
       "messages":[{"role":"user","content":"Write 200 words about disk scheduling."}],
       "stream":true,"max_tokens":256}'

time_starttransfer is het moment waarop de eerste byte van de response body arriveert. Bij een streaming chat-completion hoort die byte bij het eerste server-sent event; dit is ofwel het eerste inhoudelijke token, of een delta met alleen de rol die vlak daarvoor wordt verzonden. Beschouw de waarde daarom als de TTFT, plus of min één event. Het is nauwkeurig genoeg om twee runs op dezelfde server met elkaar te vergelijken.

Cijfers voor een enkele stream laten de hardware gunstiger lijken dan deze in de praktijk is. De TTFT is op zijn best, omdat er niets voor u in de wachtrij staat. De snelheid per stream is op zijn best, omdat de volledige videokaart één verzoek bedient. Geen van beide waarden geeft aan wat de capaciteit van de server is.

Hoe voert u een concurrency-sweep uit?

Een sweep voert één vaste werklast uit bij een stijgende concurrency en legt vast wat er bij elke stap gebeurt. vLLM levert hiervoor de client mee. Deze spreekt de OpenAI API, waardoor deze ook werkt met Ollama en andere OpenAI-compatibele systemen.

vllm bench serve \
  --backend openai-chat \
  --base-url http://127.0.0.1:8000 \
  --endpoint /v1/chat/completions \
  --model Qwen/Qwen3-8B \
  --dataset-name random \
  --random-input-len 1024 \
  --random-output-len 1024 \
  --ignore-eos \
  --num-prompts 160 \
  --max-concurrency 16 \
  --percentile-metrics ttft,tpot,itl,e2el \
  --metric-percentiles 50,99

--max-concurrency begrenst het aantal gelijktijdige verzoeken en is de variabele die u tijdens de sweep varieert. --num-prompts is het totaal aantal verzonden verzoeken; houd dit op ongeveer tien keer de concurrency voor een stabiel gemiddelde. De samenvatting toont Output token throughput (tok/s): en Total token throughput (tok/s):, gevolgd door Mean TTFT (ms):, Median TTFT (ms): en P99 TTFT (ms): onder een Time to First Token-kop.

De uitvoer bevat geen rate per stream, maar dit is eenvoudig te berekenen. Mean TPOT (ms): is de gemiddelde tijd per output-token na de eerste; 25 ms per token staat dus gelijk aan 40 tokens per seconde per stream. Het delen van de output-doorvoer door de concurrency levert hetzelfde resultaat op.

Doorloop dit proces vervolgens in een lus en sla elke run op.

for C in 1 4 16 32 64 128; do
  vllm bench serve \
    --backend openai-chat \
    --base-url http://127.0.0.1:8000 \
    --endpoint /v1/chat/completions \
    --model Qwen/Qwen3-8B \
    --dataset-name random \
    --random-input-len 1024 \
    --random-output-len 1024 \
    --ignore-eos \
    --num-prompts $(( C * 10 )) \
    --max-concurrency "$C" \
    --percentile-metrics ttft,tpot,itl,e2el \
    --metric-percentiles 50,99 \
    --save-result --result-filename "sweep-c$C.json"
done
De opgeslagen JSON-bestanden lezen

Elke run schrijft één bestand; extraheer de relevante velden uit alle bestanden tegelijk.

for f in sweep-c*.json; do
  jq -r --arg f "$f" \
    '[$f, .output_throughput, .total_token_throughput,
      .median_ttft_ms, .p99_ttft_ms] | @tsv' "$f"
done

output_throughput staat voor output-tokens per seconde. total_token_throughput telt de input-tokens erbij op; bij een 1:1-verhouding is dit bijna het dubbele. p99_ttft_ms bestaat alleen omdat --metric-percentiles 99 bevatte; als u een percentiel opvraagt dat u niet heeft aangevraagd, print jq null.

Wat toont een concurrency sweep nu eigenlijk aan?

ChartIllustrative sweep shape: 8B model, one 24 GB GPU, 1024 in / 1024 out
The data behind this chart
[
  {
    "label": "1 stream",
    "per_stream_tok_s": 92,
    "total_tok_s": 92,
    "ttft_p50_ms": 48,
    "ttft_p99_ms": 61
  },
  {
    "label": "4 streams",
    "per_stream_tok_s": 88,
    "total_tok_s": 352,
    "ttft_p50_ms": 71,
    "ttft_p99_ms": 96
  },
  {
    "label": "16 streams",
    "per_stream_tok_s": 71,
    "total_tok_s": 1136,
    "ttft_p50_ms": 152,
    "ttft_p99_ms": 244
  },
  {
    "label": "32 streams",
    "per_stream_tok_s": 54,
    "total_tok_s": 1728,
    "ttft_p50_ms": 287,
    "ttft_p99_ms": 498
  },
  {
    "label": "64 streams",
    "per_stream_tok_s": 34,
    "total_tok_s": 2176,
    "ttft_p50_ms": 611,
    "ttft_p99_ms": 1240
  },
  {
    "label": "128 streams",
    "per_stream_tok_s": 18,
    "total_tok_s": 2304,
    "ttft_p50_ms": 1490,
    "ttft_p99_ms": 3820
  }
]

Die 6 rijen zijn een illustratie van het verloop dat een sweep genereert op een kleine, huurbare GPU-server, met aannemelijke grootteordes. Het zijn geen metingen van uw eigen server en het zijn geen cijfers van een leverancier. Voer de bovenstaande loop uit en vervang ze door uw eigen resultaten.

Bestudeer het verloop, want het verloop is wat generaliseert. Bij één stream produceert de gehele server 92 tokens per seconde met een p99 TTFT van 61 ms. Bij 128 streams bereikt het totaal 2304 tokens per seconde, vijfentwintig keer hoger, terwijl elke individuele stream daalt naar 18 tokens per seconde en de p99 TTFT 3820 ms bereikt. De totale doorvoer stijgt omdat batching inactieve geheugenwachttijden omzet in nuttig werk. De snelheid per stream daalt omdat dezelfde rekenkracht nu wordt gedeeld.

De laatste verdubbeling is de indicator. Van 64 naar 128 streams gaan voegt minder dan zes procent toe aan het totaal, terwijl de p99 TTFT ruwweg verdrievoudigt; dit betekent dat de KV cache vol is en verzoeken in de wachtrij staan in plaats van worden verwerkt. Het nuttige werkpunt ligt eerder: bij 32 streams levert de server nog steeds 1728 tokens per seconde, 75 procent van zijn piek, bij 54 tokens per seconde per stream en een p99 TTFT van 498 ms. Rapporteer dat punt als uw capaciteit. De piek van de curve is een getal waarmee u geen gebruikers kunt bedienen.

Ollama en vLLM meten niet hetzelfde

Voer die sweep uit op een standaard Ollama-server en het totaal zal nauwelijks veranderen. OLLAMA_NUM_PARALLEL staat standaard op 1, waardoor één verzoek wordt uitgevoerd terwijl de rest wacht. De wachtrij zorgt ervoor dat de p99 TTFT oploopt terwijl de totale output gelijk blijft. Verhoog deze waarde voordat u metingen verricht.

sudo systemctl edit ollama.service
[Service]
Environment="OLLAMA_NUM_PARALLEL=8"

Start opnieuw met sudo systemctl restart ollama en bevestig daarna dat het model nog steeds in het geheugen past. Elke parallelle slot krijgt een eigen deel van het contextvenster. De documentatie van Ollama vermeldt daarom dat een 2K-context met 4 parallelle verzoeken 8K toewijst. Verhoog het aantal slots ver genoeg en het model loopt uit het VRAM. Controleer ollama ps: een PROCESSOR-kolom die zoiets aangeeft als 48%/52% CPU/GPU betekent dat een deel van het model zich op de CPU bevindt. De doorvoer zal nu dalen naarmate u meer gelijktijdigheid toevoegt in plaats van stijgen. Voorbij de parallelle slots komen verzoeken in de wachtrij tot OLLAMA_MAX_QUEUE, standaard 512, waarna de server een 503-foutmelding geeft.

vLLM gebruikt continuous batching, waardoor nieuwe verzoeken aan de actieve batch worden toegevoegd zodra er slots vrijkomen. De curve blijft stijgen totdat de KV-cache vol is. Ollama is geoptimaliseerd voor één model, één machine en lage installatiekosten. De twee engines geven daarom verschillende antwoorden op dezelfde sweep, wat het eigenlijke onderwerp is van Ollama en vLLM vergeleken als serving engines. Noteer welke engine en welke versie elk getal heeft geproduceerd.

Vijf manieren om de verkeerde zaken te meten

  • De client bevindt zich op grote afstand. Benchmarking vanaf uw laptop via het internet voegt uw eigen round-trip tijd toe aan elke TTFT, waardoor u in feite uw eigen internetverbinding meet. Voer de client uit in dezelfde regio als de server.
  • Het model was koud. De eerste aanvraag moet het laden van de gewichten bekostigen, en bij vLLM kan dit ook het vastleggen van de graaf (graph capture) omvatten. Stuur een opwarmingsbatch en negeer het resultaat.
  • Prefix caching gaf antwoord voor u. vLLM schakelt automatische prefix caching standaard in. Het herhaaldelijk versturen van dezelfde prompt meet daarom de cache in plaats van de prefill, waardoor de TTFT daalt tot een fractie van de werkelijke waarde. --dataset-name random voorkomt dit omdat elke prompt verschilt. Start voor de zekerheid de server met --no-enable-prefix-caching.
  • De uitvoer was te kort. Bij antwoorden van 32 tokens domineert de TTFT elk verzoek en beschrijft uw tokens per seconde in feite de prefill. Gebruik --ignore-eos met een realistische uitvoerlengte.
  • U rapporteerde een concurrency van 1. Dit is het meest gunstige getal op het overzicht en het heeft geen enkele invloed op de kosten.

Zet uw gemeten getal om in een beslissing

Gebruik de verzadigde output-doorvoer uit uw sweep, niet de single-stream snelheid, en vergelijk deze met de prijs per token. Het break-evenpunt is een eenvoudige deling:

break_even_tok_s = (price_per_hour / price_per_million_output_tokens) * 1000000 / 3600

Werk dit uit met de prijzen van DigitalOcean van juli 2026. Hun H200 dedicated inference endpoint kostte $4,47 per uur en het serverless equivalent kostte $0,65 per miljoen tokens. Dus 4,47 gedeeld door 0,65 is 6,88 miljoen tokens per uur, en delen door 3.600 seconden geeft ongeveer 1.910 output-tokens per seconde. De prijzen zijn van hen. De deling is van ons.

Het woord dat hier de doorslag geeft is duurzaam. Het behalen van 1.910 tokens per seconde bij verzadiging gedurende twee uur per dag is geen 1.910 tokens per seconde duurzaam, omdat u ook voor de overige tweeëntwintig uur betaalt. Het eigen omslagpunt van DigitalOcean voor de goedkopere GPU Droplet van $3,44 per uur ligt op 72,2 procent duurzame gemiddelde bezettingsgraad; daaronder wint de prijs per token. Inactieve GPU-uren, niet trage tokens, zijn meestal de reden waarom zelf-hosting niet rendabel is.

Uw beslissing heeft dus twee inputs. De sweep geeft u het plafond. Uw verkeerspatroon geeft u het deel van dat plafond dat u daadwerkelijk benut. Vermenigvuldig deze, neem het resultaat mee naar de GPU VPS versus het break-evenpunt van de API per token en lees het antwoord af voor uw volume.

FAQ

Wat is een goed aantal tokens per seconde voor een zelf-gehoste LLM?

Er zijn twee antwoorden, omdat de metriek twee doelen dient. Voor één persoon die de output leest, is alles boven ongeveer 20 output-tokens per seconde per stream al sneller dan de leessnelheid, dus meer helpt niet. Voor de kosten is het relevante getal de verzadigde totale output-doorvoer, en "goed" betekent hier alles wat uw break-evenpunt haalt. Bij $0,65 per miljoen tokens op een server die $4,47 per uur kost, ligt die ondergrens rond de 1.910 output-tokens per seconde, continu gemeten tegen de prijzen van juli 2026. Eén stream op een groot model bereikt dit nooit; dat is de reden waarom batching bestaat.

Waarom blijft mijn Ollama-doorvoer gelijk wanneer ik gelijktijdige verzoeken toevoeg?

OLLAMA_NUM_PARALLEL staat standaard op 1, waardoor de server per model één verzoek tegelijk verwerkt en de rest in de wachtrij plaatst, tot een maximum van OLLAMA_MAX_QUEUE (standaard 512) voordat een 503-fout wordt geretourneerd. De totale output blijft gelijk terwijl de p99 TTFT stijgt; dit is het kenmerk van een wachtrij in plaats van een zwaarbelaste GPU. Stel de variabele in via een systemd drop-in en herstart de service. Controleer daarna ollama ps, omdat elk parallel slot de toegewezen context vermenigvuldigt en een deel van het model naar de CPU kan dwingen.

Moet ik de tijd tot het eerste token (TTFT) of de tokens per seconde meten?

Beide, aangezien ze in tegengestelde richtingen bewegen naarmate de belasting toeneemt. TTFT is wat een gebruiker ervaart, en de verzadigde output-doorvoer is wat uw factuur bepaalt. Registreer de p50 en p99 TTFT bij elke stap in gelijktijdigheid en kies vervolgens de hoogste gelijktijdigheid waarbij de p99 TTFT voor u nog acceptabel is. Rapporteer de doorvoer op dat punt als uw capaciteit, niet het maximum aan de top van de curve.

Betekent een hoger aantal tokens per seconde altijd lagere kosten per token?

Nee. De kosten per token zijn de uurprijs gedeeld door het aantal tokens dat de server in dat uur daadwerkelijk heeft geproduceerd. Een snelle server die het grootste deel van de dag niets doet, heeft dus nog steeds hoge kosten per token. De bezettingsgraad bepaalt dit, niet de pieksnelheid. Let ook op de eenheden: een opgegeven totale token-doorvoer telt input-tokens mee; bij een 1:1 verhouding tussen input en output is dit bijna het dubbele van de output-snelheid waarvoor u betaalt.

#benchmarking#tokens-per-second#vllm#ollama#gpu-vps