Inference stack

Prefill vs decode, batching, paged attention, spekulace.

Co se naučíš: Pochopíš rozdíl mezi prefill a decode a proč je jeden omezený výpočtem a druhý pamětí.

7 min čtení + cvičeníNavazuje na:🔎 Attention do hloubky

Tohle je kapitola, která z „umím volat API“ dělá „rozumím tomu, co se děje na druhé straně“. Provoz modelu má vlastní fyziku: dvě zcela odlišné fáze, hlavní úzké hrdlo v paměťové propustnosti a sadu triků, díky nimž jeden server obslouží stovky uživatelů naráz.


Dvě fáze, které se chovají opačně

PREFILL (zpracování promptu)      DECODE (generování odpovědi)
─────────────────────────────      ────────────────────────────
všechny tokeny naráz               jeden token po druhém
omezeno výpočtem (compute-bound)   omezeno pamětí (memory-bound)
GPU vytížená naplno                GPU se nudí, čeká na paměť
škáluje ~kvadraticky s délkou      lineární, ale sériové
→ určuje „čas do prvního tokenu“   → určuje „tokeny za sekundu“

Klíčové zjištění, které překvapí: decode nelimituje výpočetní výkon, ale propustnost paměti. Pro každý vygenerovaný token je potřeba přečíst všechny váhy modelu z paměti karty. U modelu 14 GB a karty s propustností 1 TB/s vychází strop kolem 70 tokenů za sekundu, a to bez ohledu na to, kolik má karta výpočetního výkonu.

Odtud plyne většina optimalizací: když už se váhy jednou načtou, ať se využijí pro co nejvíc tokenů naráz.


Batching: proč je hromadné zpracování skoro zdarma

Když zpracováváš osm požadavků najednou, načteš váhy jednou a spočítáš osm tokenů. Propustnost serveru vzroste skoro osminásobně, latence jednotlivce se skoro nezhorší.

Continuous batching (též in-flight batching) to dotahuje: místo aby server čekal, až doběhne celá dávka, průběžně odebírá hotové požadavky a přidává nové. Krátký dotaz tak nečeká na dlouhý. Tohle je jediný největší důvod, proč jsou dnešní API tak levná.


Paged attention

KV cache je největší žrout paměti (viz attention do hloubky) a naivně se alokuje na maximální délku dopředu, takže se plýtvá.

Paged attention používá stejný nápad jako virtuální paměť v operačním systému: cache se ukládá po stránkách pevné velikosti, které nemusí ležet za sebou. Fragmentace zmizí, do stejné karty se vejde několikanásobek souběžných konverzací a stránky se dají mezi požadavky sdílet, třeba společný systémový prompt existuje v paměti jen jednou.


Speculative decoding

Malý rychlý model (draft) vygeneruje třeba pět tokenů dopředu, velký model je pak jedním průchodem ověří a přijme ty, na kterých se shodne.

draft:  "Paříž je hlavní město"   (5 tokenů, levně)
velký:  ověří naráz               → přijme 4, pátý přepíše

Protože ověření pěti tokenů stojí skoro stejně jako vygenerování jednoho (jsme paměťově omezení, ne výpočetně), vyjde z toho dvoj- až trojnásobné zrychlení bez ztráty kvality, výstup je matematicky totožný s tím, co by vydal velký model sám.


Prompt caching

Když má hodně požadavků stejný začátek (dlouhý systémový prompt, tatáž dokumentace), dá se jeho KV cache spočítat jednou a znovu použít. Ušetří to prefill, tedy čas do prvního tokenu i peníze. Proto poskytovatelé účtují cachované vstupní tokeny výrazně levněji a proto se vyplatí držet neměnnou část promptu na začátku.


Kvantizace při inferenci

Kromě vah se dá kvantizovat i KV cache a aktivace:

  • Váhy ve 4 bitech: standard pro lokální běh, kvalita mírně klesá.
  • FP8: dnes běžné v datacentrech, kvalitativně skoro zdarma.
  • KV cache v 8 nebo 4 bitech: zdvojnásobí až zečtyřnásobí počet souběžných uživatelů.

Co z toho plyne pro tebe jako uživatele API

  • Krátký výstup = rychlá odpověď. Délka výstupu určuje čas, ne velikost promptu.
  • Neměnnou část promptu dej na začátek a využij prompt caching.
  • Dávkuj, když zpracováváš víc položek, poskytovatelé to často účtují levněji.
  • Latence kolísá podle vytížení, protože sdílíš dávku s ostatními. Proto se měří p95, ne průměr.

Cvičení

  1. Prompt má 4 000 tokenů, odpověď 500. Která fáze zabere víc výpočtu a která víc času? Proč to není totéž?
  2. Proč dávkování (batching) zrychlí decode, ale prefillu skoro nepomůže?
Náčrt řešení: rozbal, až si cvičení zkusíš sám
  1. Výpočtu zabere víc prefill, času víc decode. Prefill zpracuje 4 000 tokenů, ale všechny naráz a paralelně, takže GPU vytíží a odbaví to rychle. Decode udělá 500 samostatných průchodů, každý s minimem výpočtu, ale při každém musí načíst z paměti všechny váhy modelu. Úzké hrdlo se tedy liší: u prefillu výpočetní jednotky, u decode propustnost paměti.
  2. Protože decode je omezený pamětí a prefill výpočtem. Při decode se váhy načtou z paměti jednou a obslouží klidně padesát souběžných požadavků, takže se cena načtení rozpustí mezi ně a propustnost roste skoro lineárně. Prefill už výpočetní jednotky vytěžuje naplno i jedním požadavkem, takže přidáním dalších se nic nezrychlí, jen se čeká ve frontě.

Shrnutí

  • Prefill je omezený výpočtem, decode propustností paměti, proto se batchuje.
  • Continuous batching a paged attention jsou důvody, proč je inference tak levná.
  • Speculative decoding zrychluje bez ztráty kvality, výstup je identický.
  • Prompt caching odměňuje stabilní začátek promptu.
Proč je generování tokenů omezené pamětí, a ne výpočetním výkonem?

Protože pro každý vygenerovaný token se musí z paměti karty přečíst všechny váhy modelu, zatímco samotných výpočtů je málo. Strop tak dává propustnost paměti, u modelu 14 GB a karty s 1 TB/s vychází kolem 70 tokenů za sekundu bez ohledu na výpočetní výkon.

Jak funguje speculative decoding a proč nezhorší kvalitu?

Malý model navrhne několik tokenů dopředu a velký je jedním průchodem ověří; přijme jen ty, na kterých by se shodl sám. Výstup je proto matematicky totožný s tím, co by vygeneroval velký model, jen se k němu dojde rychleji, protože ověření několika tokenů stojí zhruba jako generování jednoho.

Proč se vyplatí mít neměnnou část promptu na začátku?

Kvůli prompt cachingu: KV cache pro společný prefix se spočítá jednou a znovu použije, takže se ušetří prefill i peníze. Jakmile se ale začátek promptu změní, cache se nedá použít, proto se proměnlivé části dávají až za tu stabilní.