Benchmark lokalnog modela: koliko tokena u sekundi zaista dobijaš
„Sporo je" nije podatak. Bez broja ne znaš da li je model loše izabran, da li se deo slojeva preliva na procesor, ili je sve normalno i očekivanja su bila pogrešna.
Ovaj tekst pokazuje šta se meri, čime i kako se rezultat čita. Sve dalje u Fazi 5 — nadzor, kapacitet, trošak — počiva na brojevima odavde.
Šta se meri
Dve faze rade po različitim pravilima, kako je objašnjeno u tekstu o CPU i GPU inferenciji:
| Mera | Šta govori | Ograničena čime |
|---|---|---|
| Prefill (pp) | Brzina čitanja prompta | Računom |
| Decode (tg) | Brzina generisanja odgovora | Propusnošću memorije |
| TTFT | Čekanje na prvi znak | Dužinom prompta |
| Propusnost | Ukupno tokena u sekundi, svi korisnici | Postavkom servisa |
Jedan broj ne opisuje postavku. Model može da generiše šezdeset tokena u sekundi, a da korisnik čeka pola minuta na prvi znak jer je prompt dugačak. Za razgovor je bitan TTFT plus decode; za paketnu obradu jedino ukupna propusnost.
llama-bench
Najčistije merenje, bez servera i HTTP-a između:
$ llama-bench -m ~/models/qwen3-8b-q4_k_m.gguf -ngl 99
| model | size | test | t/s | | ----------------- | -------: | ------ | -------------: | | qwen3 8B Q4_K_M | 4.68 GiB | pp512 | 3421.53 ± 24.1 | | qwen3 8B Q4_K_M | 4.68 GiB | tg128 | 58.34 ± 0.42 |
Oznaka pp512 je obrada prompta od 512 tokena, tg128 generisanje 128 tokena. Odnos je tipičan — prefill je pedesetak puta brži od decode.
Merenje sa dužim promptom i različitim rasporedom slojeva:
$ llama-bench -m model.gguf -p 512,2048,8192 -n 128 -ngl 99 $ llama-bench -m model.gguf -ngl 0,20,40,99
Ovo drugo je najkorisnije merenje u celom tekstu — pokazuje tačno koliko košta svaki sloj koji ostane na procesoru:
| ngl | tg128 t/s | | --- | --------: | | 0 | 8.12 | | 20 | 14.73 | | 40 | 41.20 | | 99 | 58.34 |
Pad nije srazmeran. Kako pokazuje red sa ngl 40, poslednjih nekoliko slojeva na procesoru odnosi trećinu brzine.
Poređenje kvantizacija, uz podatak iz teksta o kvantizaciji:
$ for v in q4_k_m q5_k_m q8_0; do
echo "=== $v ==="
llama-bench -m ~/models/qwen3-8b-$v.gguf -ngl 99 -n 128
done
Alat je deo llama.cpp postavke iz teksta o kompajliranju.
Merenje kroz API
llama-bench meri model. Kroz API meriš ono što korisnik zaista dobija, sa svim slojevima između.
$ curl -s -o /dev/null -w 'ukupno: %{time_total}s\n' \
http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen3:8b","messages":[{"role":"user","content":"Nabroj deset Linux komandi"}],"max_tokens":200}'
Ollamin sopstveni put daje razložene brojeve, u nanosekundama:
$ curl -s http://localhost:11434/api/generate \
-d '{"model":"qwen3:8b","prompt":"Objasni šta je inode","stream":false}' \
| jq '{
ucitavanje: (.load_duration/1e9),
prefill_ts: (.prompt_eval_count / (.prompt_eval_duration/1e9)),
decode_ts: (.eval_count / (.eval_duration/1e9)),
ukupno: (.total_duration/1e9)
}'
{
"ucitavanje": 0.021,
"prefill_ts": 2847.3,
"decode_ts": 56.8,
"ukupno": 3.42
}
Ako je ucitavanje veliko, model je bio izbačen iz memorije — podigni OLLAMA_KEEP_ALIVE, kako stoji u tekstu o systemd jedinici.
TTFT
Merи se samo kroz streaming:
#!/bin/bash
POCETAK=$(date +%s.%N)
curl -sN http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen3:8b","messages":[{"role":"user","content":"Objasni šta je inode"}],"stream":true}' \
| while IFS= read -r red; do
case "$red" in
data:*content*)
PRVI=$(date +%s.%N)
echo "TTFT: $(echo "$PRVI - $POCETAK" | bc) s"
break
;;
esac
done
Ovaj broj je ono što korisnik doživljava kao „brzo" ili „sporo". Ispod sekunde deluje trenutno; preko tri sekunde deluje pokvareno, čak i ako je decode odličan.
Merenje pod opterećenjem
Ovde se vidi razlika između Ollame i vLLM-a, koja se sa jednim zahtevom ne primećuje.
#!/usr/bin/env python3
"""Merenje propusnosti sa N paralelnih zahteva."""
import sys, time, statistics
from concurrent.futures import ThreadPoolExecutor
import requests
URL = "http://localhost:11434/v1/chat/completions"
MODEL = "qwen3:8b"
PITANJE = "Objasni ukratko šta je systemd i čemu služi."
def jedan(_):
t0 = time.perf_counter()
o = requests.post(URL, json={
"model": MODEL,
"messages": [{"role": "user", "content": PITANJE}],
"max_tokens": 200,
"temperature": 0,
}, timeout=300)
trajanje = time.perf_counter() - t0
if o.status_code != 200:
return None
return trajanje, o.json()["usage"]["completion_tokens"]
def meri(paralelno, ukupno=None):
ukupno = ukupno or paralelno * 3
t0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=paralelno) as izvrsilac:
rezultati = [r for r in izvrsilac.map(jedan, range(ukupno)) if r]
proteklo = time.perf_counter() - t0
if not rezultati:
print(f"{paralelno:3d} — svi zahtevi neuspeli")
return
trajanja = [t for t, _ in rezultati]
tokeni = sum(n for _, n in rezultati)
print(f"{paralelno:3d} paralelno | "
f"ukupno {tokeni/proteklo:7.1f} t/s | "
f"po zahtevu {tokeni/len(rezultati)/statistics.mean(trajanja):5.1f} t/s | "
f"medijana {statistics.median(trajanja):5.2f} s | "
f"neuspelih {ukupno-len(rezultati)}")
if __name__ == "__main__":
for n in [1, 2, 4, 8, 16]:
meri(n)
time.sleep(2)
$ python3 opterecenje.py 1 paralelno | ukupno 56.2 t/s | po zahtevu 56.2 t/s | medijana 3.56 s | neuspelih 0 2 paralelno | ukupno 58.9 t/s | po zahtevu 29.4 t/s | medijana 6.81 s | neuspelih 0 4 paralelno | ukupno 61.3 t/s | po zahtevu 15.3 t/s | medijana 13.07 s | neuspelih 0 8 paralelno | ukupno 62.0 t/s | po zahtevu 7.7 t/s | medijana 25.94 s | neuspelih 0
Ovo je Ollama. Ukupna propusnost stoji, a pojedinačni korisnik dobija sve manje — zahtevi se rede, ne obrađuju uporedo.
Isti test na vLLM-u:
1 paralelno | ukupno 52.4 t/s | po zahtevu 52.4 t/s | medijana 3.82 s | neuspelih 0 2 paralelno | ukupno 101.7 t/s | po zahtevu 50.8 t/s | medijana 3.94 s | neuspelih 0 4 paralelno | ukupno 196.3 t/s | po zahtevu 49.1 t/s | medijana 4.07 s | neuspelih 0 8 paralelno | ukupno 378.9 t/s | po zahtevu 47.4 t/s | medijana 4.22 s | neuspelih 0
Propusnost raste gotovo linearno, a pojedinačni korisnik jedva primećuje da nije sam. To je ceo argument za vLLM, i on postoji samo pod opterećenjem — sa jednim zahtevom vLLM je čak nešto sporiji.
Tokom testa prati karticu u drugom terminalu:
$ watch -n 1 nvidia-smi
Potrošnja blizu granice znači da je kartica zasićena i da dalje podizanje broja zahteva ne donosi ništa. Čitanje ispisa pokriva tekst o nvidia-smi.
Poštena merenja
Nekoliko pravila bez kojih brojevi ne znače ništa.
Zagrej pre merenja. Prvi zahtev uključuje učitavanje modela. Baci ga.
Fiksiraj dužinu izlaza. Bez max_tokens model jednom napiše sto tokena, drugi put četiristo, i poređenje otpada.
Temperatura na nulu. Isti ulaz, isti izlaz, ista dužina.
Meri više puta i uzmi medijanu. Jedno merenje nije podatak.
Zapiši uslove. Model, kvantizacija, kontekst, verzija alata, temperatura kartice. Bez toga poređenje sa merenjem od pre mesec dana ne vredi.
$ cat > merenje.txt <<EOF Datum: $(date -Is) Host: $(hostname) Kartica: $(nvidia-smi --query-gpu=name --format=csv,noheader) Drajver: $(nvidia-smi --query-gpu=driver_version --format=csv,noheader) Model: qwen3:8b Q4_K_M Alat: $(ollama --version) Kontekst: 16384 EOF
Proveri da nema termalnog usporavanja. Duži test podigne temperaturu, pa poslednja merenja budu lošija bez ikakvog drugog razloga:
$ nvidia-smi --query-gpu=clocks_throttle_reasons.active,temperature.gpu --format=csv
Čitanje rezultata
| Nalaz | Šta znači |
|---|---|
| Decode ispod 10 t/s na kartici | Deo slojeva je na procesoru |
| Prefill blizu decode brzine | Model uopšte ne radi na kartici |
| Visok TTFT, dobar decode | Prompt je predugačak ili se model učitava |
| Propusnost ne raste sa korisnicima | Ollama umesto vLLM-a, ili pun KV keš |
| Brzina opada kroz test | Termalno usporavanje |
| Velik rasip između merenja | Nešto drugo koristi karticu |
Šta je dobar broj
Za kontekst, iz ranijeg teksta: ispod deset t/s je za obradu u pozadini, oko petnaest je granica udobnosti za razgovor, preko trideset je prijatno.
Gruba provera da li je tvoj broj razuman — propusnost memorije kartice podeljena veličinom modela daje teorijski maksimum. Realno dobijaš pedeset do sedamdeset posto toga. Ako si znatno ispod, nešto nije u redu.
Praktični scenariji
Scenario 1: llama-bench pokazuje dobro, kroz API je sporo
Problem je iznad modela — proxy, mreža, ili preveliki podrazumevani kontekst. Meri redom, sloj po sloj.
Scenario 2: brzina se razlikuje između pokretanja
Model se izbacuje iz memorije. Proveri load_duration i podigni keep-alive.
Scenario 3: prvi test brz, poslednji spor
Kartica se zagrejala. Proveri razlog usporavanja i ubaci pauzu između merenja.
Scenario 4: propusnost pada pri osam zahteva
KV keš je pun. Kod vLLM-a proveri gpu_cache_usage_perc na /metrics.
Scenario 5: rezultat ne liči na ono što drugi objavljuju
Verovatno se meri nešto drugo — druga dužina prompta, druga kvantizacija, ili propusnost umesto brzine po korisniku. Poredi samo svoje merenje sa svojim.
Scenario 6: sve deluje dobro, korisnici se žale
Meriš decode, a njima smeta TTFT. Izmeri ga posebno.
Kratka referenca
llama-bench -m model.gguf -ngl 99— merenje bez serverallama-bench -ngl 0,20,40,99— cena prelivanja na procesorllama-bench -p 512,2048,8192— uticaj dužine promptapp— prefill,tg— decode/api/generate+jq— razloženi brojevi kod Ollameload_duration— ako je velik, model je bio izbačen- TTFT — meri se samo kroz streaming
curl -w '%{time_total}'— ukupno vreme zahteva/metricskod vLLM-a —gpu_cache_usage_perc- Zagrej, fiksiraj
max_tokens, temperatura 0, medijana clocks_throttle_reasons.active— provera usporavanja- Ollama i vLLM se razlikuju samo pod opterećenjem
Vežba
- Izmeri prefill i decode svog modela kroz
llama-bench. - Pokreni merenje sa
-ngl 0,20,40,99i nacrtaj kako brzina pada. - Izmeri TTFT za kratak i za dugačak prompt, pa uporedi.
- Pokreni test opterećenja sa 1, 4 i 8 paralelnih zahteva i objasni oblik rezultata.
- Ako imaš i vLLM, ponovi isti test i uporedi obe kolone.
- Zapiši uslove merenja u fajl, pa ponovi merenje za nedelju dana i uporedi.
Sledeći tekst u serijalu: Monitoring inferencije sa Prometheusom i Grafanom
Povezano:
- AI na Linux serveru — pregled celog serijala
- RAG nad sopstvenom dokumentacijom — prethodni tekst
- CPU inferencija naspram GPU inferencije — odakle brojevi dolaze
- nvidia-smi i nvtop — praćenje kartice tokom merenja
- vLLM — alat čija se prednost vidi tek pod opterećenjem
Comments
Post a Comment