Latens, throughput och batching vid inferens
Kunna mäta och resonera om latens kontra throughput och kontinuerlig batching.
Förkunskaper
- EKV-cachekrävs
Intuition
Tre tal som ofta blandas ihop:
| Mått | Vad | Vem bryr sig |
|---|---|---|
| TTFT (time to first token) | hur snabbt något börjar hända | användaren — det här känns som «snabbhet» |
| TPOT (tid per token) | hur snabbt texten rullar ut | användaren, vid långa svar |
| Throughput | tokens per sekund totalt över alla användare | den som betalar hårdvaran |
De står i konflikt. Större batch → högre throughput, sämre TTFT för den enskilde. Du måste välja vilket krav som styr — och mäta båda.
En tumregel som håller: TTFT under 1 s och 20+ tokens/s upplevs som snabbt i chatt. Under 10 tokens/s känns det trögt även om svaret är bra.
Formellt
Statisk batching väntar in N förfrågningar, kör dem tillsammans och släpper alla när den längsta är klar. En förfrågan som genererar 20 tokens väntar på en som genererar 800. Utnyttjandegraden blir usel.
Continuous batching (Orca, vLLM) arbetar på iterationsnivå: efter varje avkodningssteg släpps färdiga sekvenser ut och nya tas in i deras plats. Effekten är 2–10× högre throughput vid samma latenskrav — den enskilt största vinsten i en LLM-tjänst.
Köteori räcker för att sätta förväntningar: med ankomstintensitet λ och servicekapacitet μ växer väntetiden mot oändligheten när utnyttjandegraden ρ = λ/μ närmar sig 1. Planera för ρ ≈ 0,6–0,7 vid p95-krav; kör du på 0,95 har du ingen marginal för trafiktoppar.
Mätprotokoll som ger jämförbara tal: fast promptlängd och fast antal genererade tokens, uppvärmning före mätning, median och p95 (inte medelvärde), och belastning från flera samtidiga klienter — annars mäter du bara enanvändarfallet.
Kod
import asyncio, time, numpy as np
async def en_forfragan(klient, prompt, max_tokens=128):
t0 = time.perf_counter(); forsta = None; n = 0
async for _tok in klient.stream(prompt, max_tokens=max_tokens):
if forsta is None:
forsta = time.perf_counter() - t0
n += 1
total = time.perf_counter() - t0
return {"ttft": forsta, "tpot": (total - forsta) / max(n - 1, 1), "total": total, "tokens": n}
async def belastningstest(klient, prompt, samtidiga=16, per_klient=5):
t0 = time.perf_counter()
res = await asyncio.gather(*[en_forfragan(klient, prompt)
for _ in range(samtidiga * per_klient)])
vagg = time.perf_counter() - t0
ttft = np.array([r["ttft"] for r in res]); tpot = np.array([r["tpot"] for r in res])
return {"samtidiga": samtidiga,
"ttft_p50_ms": round(np.percentile(ttft, 50) * 1000),
"ttft_p95_ms": round(np.percentile(ttft, 95) * 1000),
"tokens_per_s_per_anv": round(1 / np.median(tpot), 1),
"throughput_tokens_per_s": round(sum(r["tokens"] for r in res) / vagg)}
# samtidiga=1: ttft_p50 180 ms, 45 tok/s/anv, throughput 45
# samtidiga=16: ttft_p50 520 ms, 28 tok/s/anv, throughput 448 ← 10× throughput, 3× TTFT
Behärskning innebär
- Mäter TTFT, tid per token och throughput separat
- Förklarar continuous batching
- Väljer batchstrategi efter krav
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- arXiv — Efficient Memory Management for LLM Serving with PagedAttention (vLLM) — arXiv (öppen åtkomst; licens per artikel)
- arXiv — Orca: A Distributed Serving System for Transformer-Based Generative Models — arXiv (öppen åtkomst; licens per artikel)