Model nevydává slova, ale logits, skóre pro každý token ve slovníku. Cesta od nich ke konkrétnímu tokenu je krátká, ale je v ní překvapivě hodně praktické páky. A na jejím konci stojí trik, díky kterému jde formát výstupu vynutit se stoprocentní jistotou, ne o něj jen poprosit.
Od logits k tokenu
logits (jedno číslo pro každý z ~100 000 tokenů)
│
├─ 1. logit bias / penalizace (ruční úpravy skóre)
├─ 2. maskování (zakázané tokeny → −∞)
├─ 3. dělení temperature (zplošti / zaostři rozdělení)
├─ 4. softmax (skóre → pravděpodobnosti)
├─ 5. ořez top-k / top-p / min-p
└─ 6. losování jednoho tokenu
Každý krok je jednoduchý a všechny se dají ovlivnit z API.
Na pořadí záleží. Temperature se aplikuje na logits před softmaxem, ořezy až na hotové pravděpodobnosti. Kdybys to prohodil, temperature by ořez zrušila.
Temperature a ořezy
Temperature dělí logits před softmaxem. Nízká hodnota rozdíly zvětší (model se drží
nejpravděpodobnějšího), vysoká je srovná (víc náhody). temperature = 0 znamená prostě vzít maximum.
Top-k nechá jen k nejlepších tokenů. Jednoduché, ale hloupé: u jasného pokračování zbytečně připouští alternativy, u nejisté situace jich naopak odřízne moc.
Top-p (nucleus) bere tokeny, dokud se jejich pravděpodobnosti nesečtou na p. Adaptivní, u jistého pokračování zbude jeden token, u nejistého klidně padesát. Proto vytlačil top-k.
Min-p je novější varianta: nech jen tokeny, které mají aspoň daný zlomek pravděpodobnosti toho nejlepšího. Chová se lépe při vyšších teplotách.
💡 Používej jednu páku. Kombinace temperature + top-p + top-k naráz vede k tomu, že nevíš, co ti co způsobilo.
Zkus si, kolik tokenů vlastně přežije
▶ Spustitelné. Ulož jako
vzorkovani.pya pusťpython3 vzorkovani.py. Potřebuješ jen NumPy.
"""Od logits k tokenu: co přesně udělá temperature a co ořezy."""
import numpy as np
def softmax(z):
e = np.exp(z - z.max()); return e / e.sum()
rng = np.random.default_rng(0)
V = 50000
logits = rng.normal(0, 2, V) # syrová skóre pro celý slovník
logits[[7, 42, 123, 999]] += [9.0, 8.2, 7.5, 6.9] # pár kandidátů, co dávají smysl
def top_p(logits, temp=1.0, p=0.9):
probs = softmax(logits / temp)
order = np.argsort(-probs) # od nejpravděpodobnějšího
cum = np.cumsum(probs[order])
keep = order[:np.searchsorted(cum, p) + 1] # nejmenší množina se součtem >= p
out = np.zeros_like(probs)
out[keep] = probs[keep] / probs[keep].sum() # přeškálovat na součet 1
return out, len(keep)
for temp in (0.2, 0.7, 1.0, 1.5):
for p in (0.9, 0.95):
_, n = top_p(logits, temp, p)
print(f'temperature {temp} top-p {p} -> přežije {n:5d} tokenů z {V}')
print()
probs = softmax(logits)
print(f'bez ořezu: nejlepší token má {probs.max():.3f}, '
f'ale zbylých {V-1} tokenů dohromady {1-probs.max():.3f}')
print(f'to znamená, že šance na nesmysl je při čistém losování {1-probs.max():.1%}')
Výstup:
temperature 0.2 top-p 0.9 -> přežije 2 tokenů z 50000
temperature 0.7 top-p 0.9 -> přežije 106 tokenů z 50000
temperature 0.7 top-p 0.95 -> přežije 694 tokenů z 50000
temperature 1.0 top-p 0.9 -> přežije 9424 tokenů z 50000
temperature 1.5 top-p 0.9 -> přežije 23603 tokenů z 50000
bez ořezu: nejlepší token má 0.109, ale zbylých 49999 tokenů dohromady 0.891
to znamená, že šance na nesmysl je při čistém losování 89.1%
Tady je vidět, proč se ta dvě čísla ladí společně a ne zvlášť. Při temperature 0,2 je
top_p úplně jedno, protože po zaostření rozdělení zbydou stejně dva kandidáti. A naopak při
temperature 1,5 ti top_p 0,9 pustí přes dvacet tisíc tokenů, takže ořez prakticky nic
neořezává.
Poslední dva řádky jsou důvod, proč se vůbec ořezává: i u rozumného rozdělení má ten nejlepší token jen deset procent, zatímco dlouhý ocas nesmyslů má dohromady devadesát. Bez ořezu bys z něj losoval skoro pokaždé.
Penalizace opakování
Tři mechanismy, které často pletou dohromady:
- presence_penalty: odečte konstantu od logitu tokenu, který se už objevil (jednou a dost).
- frequency_penalty: odečte tím víc, čím častěji se token objevil.
- repetition_penalty: logit už použitého tokenu vydělí (nebo vynásobí) konstantou.
Všechny jsou hrubé: nerozlišují mezi „opakuješ se“ a „tohle slovo se prostě v textu vyskytuje často“. Když se model zacyklí, bývá lepší opravit prompt nebo zvýšit teplotu než sahat po penalizacích.
Proč se u chatu nepoužívá beam search
Beam search drží několik nejlepších pokračování a vybere celkově nejpravděpodobnější sekvenci. Pro překlad je to výborné. Pro konverzaci ne: nejpravděpodobnější text je nudný a repetitivní, model sklouzne k obecným frázím a opakování. Proto se u chatu vzorkuje.
Řízené dekódování: jak se formát vynutí
Tohle je nejelegantnější část celé kapitoly. Chceš-li mít jistotu, že výstup bude validní JSON, nepotřebuješ prosbu v promptu, stačí zakázat tokeny, které by ho porušily.
stav: právě jsme napsali {"jmeno": "Ja
povolené pokračování podle JSON gramatiky:
libovolný znak řetězce, nebo uzavírací uvozovka
zakázané: { [ , : …
→ logity zakázaných tokenů nastav na −∞
→ model si prostě nemůže vybrat špatně
Funguje to tak, že se schéma (nebo gramatika) převede na konečný automat. V každém kroku automat řekne, které tokeny jsou přípustné, a ostatní se maskují. Výsledek je zaručeně platný podle konstrukce, ne podle štěstí.
Používá se to na:
- JSON schema: API poskytovatelů dnes běžně nabízí,
- volání nástrojů: argumenty musí sedět na deklarované schéma,
- výběr z možností: když chceš jen
ANOneboNE, zakaž všechno ostatní, - vlastní gramatiky (regulární výrazy, SQL, DSL) u lokálních modelů.
⚠️ Vynucený tvar není vynucený obsah. Model může vrátit dokonale validní JSON s vymyšleným ID. Kontrola hodnot zůstává na tobě, viz strukturovaný výstup.
Proč ani temperature 0 nedá pokaždé totéž
Několik důvodů dohromady: pořadí operací s plovoucí čárkou na GPU není deterministické, výsledek závisí na tom, s kým se tvůj požadavek sešel v dávce, a poskytovatel může model kdykoli vyměnit za novější build. Ber to jako fakt a testuj strukturu a význam, ne doslovnou shodu.
Shrnutí
- Z logits se dostaneš k tokenu přes penalizace, maskování, temperature, softmax a ořez.
- Top-p je adaptivní a v praxi lepší než top-k; kombinuj co nejméně pák naráz.
- Beam search se u konverzace nepoužívá, protože nejpravděpodobnější text je nudný.
- Formát se vynucuje maskováním logitů podle gramatiky, je to zaručené konstrukcí, ne prosbou.
Jak API zaručí, že výstup bude validní JSON?
Schéma se převede na konečný automat, který v každém kroku určí, které tokeny jsou přípustné. Logity všech ostatních se nastaví na minus nekonečno, takže si je model nemůže vybrat. Platnost tak vyplývá z konstrukce, ne ze štěstí, hodnoty uvnitř ale správné být nemusí.
Proč se u chatovacích modelů nepoužívá beam search?
Protože hledá nejpravděpodobnější celou sekvenci, a ta bývá nudná a repetitivní, model sklouzne k obecným frázím. U překladu, kde je jedna správná odpověď, dává smysl; u konverzace se proto vzorkuje.
Jaký je rozdíl mezi top-k a top-p?
Top-k vždy nechá pevný počet nejlepších tokenů bez ohledu na situaci. Top-p bere tokeny, dokud jejich pravděpodobnosti nedosáhnou zvoleného podílu, takže se adaptuje: u jistého pokračování nechá jeden token, u nejistého mnoho. Proto se dnes používá spíš top-p.
