Parametr modelu je desetinné číslo a záleží na tom, kolika bity ho uložíš. Tahle volba rozhoduje o tom, jestli se trénink vůbec podaří, kolik model zabere paměti a jak rychle poběží. Kapitola vysvětlí, proč bf16 vyhrál nad fp16, co se děje uvnitř kvantizace a proč jsou některé váhy citlivější než jiné.
Jak vypadá desetinné číslo v paměti
Každý formát dělí bity mezi exponent (rozsah, jak velká a malá čísla umí) a mantisu (přesnost, kolik platných číslic):
| Formát | Bitů | Exponent | Mantisa | Rozsah | Přesnost |
|---|---|---|---|---|---|
| FP32 | 32 | 8 | 23 | obrovský | vysoká |
| BF16 | 16 | 8 | 7 | stejný jako FP32 | nízká |
| FP16 | 16 | 5 | 10 | malý | střední |
| FP8 | 8 | 4–5 | 2–3 | malý | velmi nízká |
Proč bf16 vyhrál
FP16 má víc bitů na mantisu, takže je přesnější. Přesto se pro trénink používá bf16. Důvod: gradienty mají obrovský dynamický rozsah: od hodnot řádu jednotek po miliardtiny.
FP16: hodnoty pod ~6e−5 spadnou na nulu (underflow)
hodnoty nad ~65 000 přetečou na nekonečno (overflow)
BF16: stejný rozsah jako FP32, jen hrubší krok
U tréninku je katastrofa, když ti gradient tiše spadne na nulu, parametr se přestane učit a nikdo si toho nevšimne. Hrubší přesnost naopak tolik nevadí, protože se chyby přes miliony kroků průměrují.
S fp16 se to dalo zachránit loss scalingem (loss se před backwardem vynásobí, aby se malé gradienty vešly do rozsahu, a pak se zase vydělí), ale je to křehké. Bf16 problém odstranil konstrukčně, a proto ho dnes používá prakticky každý trénink.
Mixed precision je standardní recept: výpočty v bf16 (rychlé, úsporné), ale master kopie vah a stavy optimizeru ve fp32, jinak by se malé úpravy vah v hrubém formátu ztratily zaokrouhlením.
Klíčové je to rozdělení na skupiny. Kdybys použil jedno měřítko na celou matici, jediná extrémní hodnota by roztáhla stupnici tak, že by se všechny běžné váhy zaokrouhlily na nulu.
Kvantizace: co se děje uvnitř
Kvantizace zmenší váhy po tréninku. Princip je jednoduchý: pro skupinu čísel se najde měřítko a čísla se na něj zaokrouhlí.
skupina vah: [0.42, −0.13, 0.87, −0.55] (fp16)
scale = max(|w|) / 7 (pro 4 bity se znaménkem: −8…7)
uloží se celá čísla + jedno scale na skupinu
Zásadní je, jak velká skupina sdílí jedno měřítko:
- Per-tensor (jedno měřítko na celou matici), nejmenší režie, největší ztráta.
- Per-channel: měřítko na každý sloupec; výrazně lepší.
- Group-wise (typicky po 64 nebo 128 hodnotách), dnešní standard, dobrý kompromis.
Zkus si to na vlastní matici
▶ Spustitelné. Ulož jako
kvantizace.pya pusťpython3 kvantizace.py. Potřebuješ jen NumPy.
"""Kvantizace do 4 bitů: proč se měřítko počítá po skupinách a ne na celou matici."""
import numpy as np
rng = np.random.default_rng(0)
W = rng.normal(0, 0.02, (256, 256)) # typické váhy natrénovaného modelu
W[7, 30] = 1.4 # jeden outlier, v reálných modelech běžné
def kvantizuj(x, bits=4):
qmax = 2 ** (bits - 1) - 1 # 4 bity se znaménkem: -7..7
scale = np.abs(x).max() / qmax
q = np.round(x / scale).clip(-qmax, qmax)
return q * scale # dekvantizace zpět
def chyba(a, b): return np.abs(a - b).mean() / np.abs(a).mean()
# 1) jedno měřítko na celou matici
glob = kvantizuj(W)
# 2) vlastní měřítko pro každou skupinu 64 hodnot
sk = W.reshape(-1, 64).copy()
skup = np.stack([kvantizuj(g) for g in sk]).reshape(W.shape)
print(f'rozsah vah: {W.min():.3f} až {W.max():.3f} (bez outlieru do {np.sort(np.abs(W).ravel())[-2]:.3f})')
print(f'jedno měřítko na celou matici : průměrná relativní chyba {chyba(W, glob):6.1%}')
print(f'měřítko po skupinách po 64 : průměrná relativní chyba {chyba(W, skup):6.1%}')
print()
print(f'kolik vah spadlo na nulu, jedno měřítko: {(glob == 0).mean():.1%}')
print(f'kolik vah spadlo na nulu, po skupinách : {(skup == 0).mean():.1%}')
print()
print(f'cena za to: {W.size // 64} čísel navíc na měřítka, '
f'tedy {W.size // 64 * 16 / (W.size * 4) * 100:.1f} % k velikosti')
Výstup mluví sám za sebe:
rozsah vah: -0.090 až 1.400 (bez outlieru do 0.095)
jedno měřítko na celou matici : průměrná relativní chyba 99.9%
měřítko po skupinách po 64 : průměrná relativní chyba 11.6%
kolik vah spadlo na nulu, jedno měřítko: 100.0%
kolik vah spadlo na nulu, po skupinách : 14.7%
cena za to: 1024 čísel navíc na měřítka, tedy 6.2 % k velikosti
Jedna jediná hodnota 1,4 v matici, kde je všechno ostatní pod 0,1, roztáhne stupnici tak,
že se všech ostatních 65 535 vah zaokrouhlí na nulu. Matice je po kvantizaci doslova
prázdná. Se skupinami po 64 hodnotách outlier pokazí jen svou vlastní skupinu a zbytek matice
přežije s chybou kolem jedenácti procent.
A stojí to 6 % místa navíc. To je celá odpověď na otázku, proč se to dělá po skupinách.
Outliers: proč to není tak snadné
V aktivacích velkých modelů se objevují extrémní hodnoty, pár kanálů má hodnoty o řády větší než zbytek. Když je zahrneš do společného měřítka, celý zbytek skupiny se zaokrouhlí na kaši.
Metody kvantizace se s tím vypořádávají různě:
- GPTQ kvantizuje váhy postupně a po každém kroku upraví zbývající, aby kompenzoval vzniklou chybu (využívá informaci o zakřivení ztrátové funkce).
- AWQ vychází z pozorování, že malá část vah je nepoměrně důležitá, pozná je podle velikosti odpovídajících aktivací a chrání je před hrubým zaokrouhlením.
- Smíšené řešení nechá citlivé vrstvy (typicky normalizace, embeddingy a poslední projekci) v plné přesnosti a zbytek zkvantizuje.
To vysvětluje, proč jsou kvalitně kvantizované modely znatelně lepší než naivně zaokrouhlené, i když mají stejný počet bitů.
QLoRA: kvantizace při ladění
Elegantní kombinace: základní model se zmrazí ve 4 bitech (nešetří se na něm nic, jen se čte) a trénuje se jen malý adaptér v bf16. Gradienty tečou skrz kvantizovaný model, ale mění se zlomek procenta parametrů. Díky tomu se dá doladit model o desítkách miliard parametrů na jediné běžné kartě.
Co si z toho vzít prakticky
- Pro trénink bf16 + fp32 master váhy. Fp16 dnes nemá důvod.
- Pro inference 4bit group-wise od někoho, kdo použil GPTQ nebo AWQ, ne vlastní naivní zaokrouhlení.
- Když model po kvantizaci přestane držet formát nebo počítat, zkus vyšší přesnost; první obětí bývá pečlivost, ne plynulost jazyka.
- FP8 v datacentru je dnes běžný a kvalitativně skoro zdarma, proto poskytovatelé zlevňují.
Kam dál
Co přesně se ve fp32 drží při tréninku, rozebírá optimizer,
důsledky pro velikost modelu velikost a kvantizace a
čtení specifikace modelu. Když ti trénink spadne na NaN,
koukni do když se trénink pokazí.
Shrnutí
- BF16 má stejný rozsah jako FP32, jen hrubší krok, proto se s ním trénuje bezpečně.
- Mixed precision počítá v bf16, ale váhy a stavy optimizeru drží ve fp32.
- Kvantizace = zaokrouhlení na sdílené měřítko; rozhoduje velikost skupiny.
- Outliers v aktivacích jsou důvod, proč existují GPTQ a AWQ místo prostého zaokrouhlení.
Proč se pro trénink používá bf16, i když má fp16 víc bitů na přesnost?
Protože bf16 má stejný exponent jako fp32, a tedy stejný dynamický rozsah. Gradienty se pohybují v obrovském rozpětí hodnot a ve fp16 by malé gradienty tiše propadly na nulu. Hrubší přesnost naopak nevadí, protože se chyby přes miliony kroků průměrují.
Co jsou outliers v aktivacích a proč komplikují kvantizaci?
Několik kanálů má hodnoty o řády větší než zbytek. Když se do společného měřítka zahrnou, ostatní hodnoty ve skupině se zaokrouhlí příliš hrubě a model ztratí kvalitu. Proto metody jako AWQ citlivé váhy chrání a GPTQ chybu průběžně kompenzuje.
Jak funguje QLoRA a proč umožní ladit velký model na jedné kartě?
Základní model se zmrazí ve 4bitové kvantizaci a trénuje se jen malý adaptér v bf16. Nepotřebuješ tedy držet gradienty a stavy optimizeru pro všechny parametry, ale jen pro zlomek, paměťové nároky klesnou o řád.
