Ez az egység a numerikus pontosság és a memóriahatár együttes kezelését vizsgálja. Előfeltétele 24.2. CPU/GPU profiling és helyes időmérés: a mért időintervallum, a gyorsítós szinkronizáció és a memóriamérés fogalmai. Visszautalunk 23.3. Páros összehasonlítás és csoportszintű intervallum páros összehasonlítási szerkezetére, 20.3. Preprocessing, domain shift és súlylicenc preprocessing-szerződésére és 12.3. Sanity check és kis mintára túlillesztés sanity checkjére. Induló kérdés: ha egy elemű batch-et darab elemű mikro-batch-re bontunk, és a gradienseket felhalmozva frissítünk, ugyanazt a paraméterlépést kapjuk-e, mint a teljes 16 elemű batch-csel?
Kevert pontosságú tanítás (mixed precision training). A modell paramétereit float32 master-súlyként tároljuk, de a forward és backward egy részét alacsonyabb pontosságú float16 vagy bfloat16 aritmetikával végezzük. A float32 normált értéktartománya hozzávetőleg és között van, 23 mantisszabittel; a float16 legkisebb pozitív normált értéke , legnagyobb értéke , 10 mantisszabittel. A bfloat16 kitevőtartománya a float32-éhez hasonló (8 kitevőbit), de csak 7 mantisszabittel.
Autocast. A torch.amp.autocast
kontextuskezelő, amely operátoronként választja meg, hogy az adott
művelet alacsony vagy teljes pontosságon fusson. A kiválasztott
pontosságot a device_type és a dtype
argumentum adja meg. A PyTorch 2.14 jelölésrendszerében a
torch.autocast korábbi elnevezés; az újabb ajánlott felület
a torch.amp.autocast (a pontos API-állapotot lásd a
forrásjegyzéknél).
GradScaler. A torch.amp.GradScaler a
loss értéket egy
skálával megszorozza, mielőtt a backward lefutna
(alapértelmezés: kezdő skála
,
growth_factor=2, backoff_factor=0.5,
growth_interval=2000). Ha a skálázatlan gradiens olyan
kicsi, hogy float16-ban alulcsordulna, a skálázott
már ábrázolható maradhat. Az unscale_ hívás a gradiensből a
skálát visszaosztja, ezután következik az optimizer-lépés. Ha a
skálázott gradiensben inf vagy NaN jelenik meg, a
scaler.step kihagyja az optimizer-lépést, az
update pedig a skálát a backoff_factor-ral
csökkenti; ismétlődő túlcsordulásnál a skála 1 alá is mehet, a PyTorch
dokumentációja ([D14], „Gradient Scaling” szakasz) kifejezetten jelzi,
hogy a skála 1 alá csökkenhet, és ezt a GradScaler nem akadályozza meg.
Lab-profilban ellenőrizve: init_scale=1.0 mellett három
végtelen gradiensű lépés után a skála
,
,
,
a súly pedig változatlan, mert mindhárom lépés kimaradt. Ez heurisztikus
dinamikus skálázás, nem általános numerikus garancia. A skála futás
közben változó állapot, ezért a tanítás folytathatóságához a
scaler.state_dict() is a checkpoint része (12.2. Checkpoint és folytathatóság).
Gradiensakkumuláció. Legyen a teljes batch
,
amelyet
darab egyenlő méretű
mikro-batch-re bontunk, ahol
és
.
Az akkumuláció során minden mikro-batch-re kiszámítjuk a mean-redukciójú
loss-t, meghívjuk a backward-ot, de a paraméterfrissítést
csak
lépés után végezzük el. A mikro-batch loss-t
-val
osztjuk, vagy a felhalmozott gradienst a frissítés előtt osztjuk
-val;
a két konvenció nem felcserélhető a clipping és a GradScaler
sorrendjében.
Gradiens-clipping. Egy küszöb esetén a teljes paramétervektor gradiensére
A norma a modell összes paraméterének gradiensét egyetlen vektorként
kezeli. Kevert pontosságban a clippinget az unscale_
után kell végezni, különben a küszöb
-szeres
skálán hat.
A hatékony batchméret (effective batch size) az szorzat; a memóriában azonban egyszerre csak egy elemű mikro-batch aktivációi élnek. A nagyobb batch a gradiensbecslés varianciáját csökkentheti (6.3. Sztochasztikus és minibatch gradiens), de a memóriaigényt növeli; az akkumuláció ezt a feszültséget oldja fel a frissítési ritmus megtartása mellett.
Tétel (gradiensakkumuláció ekvivalenciája). Legyen a modell paraméterezett függvénye, . Tegyük fel, hogy
(F1) minden -re a per-minta veszteség differenciálható -ban;
(F2) értéke nem függ a bemeneti batch többi elemétől, és a modell nem tartalmaz batch-szintű állapotot vagy a hívások közötti véletlen műveletet;
(F3) a mikro-batch-ek azonos méretűek, , és a paramétereket nem frissítjük a mikro-batch-ek között.
Ekkor a mikro-batch mean-loss gradienseinek átlaga megegyezik a teljes batch mean-loss gradiensével:
Bizonyítás (saját levezetés). A gradiens linearitása miatt a jobb oldal
Az (F2) feltétel garantálja, hogy minden ugyanaz, mint a teljes batchben számolva; az (F1) differenciálhatóság pedig a gradiens létezését. A bal oldal:
Az (F3) miatt , tehát a két oldal egyenlő.
Megjegyzés (eltérő mikro-batch méretek). Ha a mikro-batch méretek nem egyenlők, a súlyozatlan átlag általában nem egyezik a nagy batch gradiensével. A korrigált súlyozás adja a nagy batch mean-gradiensét, mert
Az akkumulációs hurkok ezért tipikusan azonos mikro-batch méretet
használnak, vagy a loss-t a batch_size / n aránnyal
súlyozzák.
Mi töri meg az egyezést? A tétel (F2) feltétele kimondottan szigorú. Két gyakori réteg ellenpélda:
BatchNorm train módban. A BatchNorm a forward során a mikro-batch saját átlagát és varianciáját használja. Az , , , négyelemű batch átlaga ; a mikro-batch átlaga . A normalizált érték a két esetben eltér, ezért a loss és a gradiens is eltér. A BatchNorm tehát nem batch-független a train módban.
Dropout. A dropout minden forward híváskor új véletlen maszkot húz. A nagy batch egyetlen forward híváskor maszkot fogyaszt; az akkumuláció darab elemű forward híváskor más RNG-sorrendben fogyaszt maszkokat. Még azonos seed mellett sem garantált, hogy a realizált dropout-maszkok megegyeznek a nagy batch realizációjával. A várható érték ugyanaz lehet, de a realizált paramétertrajektória nem.
A BatchNorm és dropout miatt tehát az azonos effektív batchméret nem garantál azonos tréninget.
Legyen az egyparaméteres modell , a négyzetes per-minta veszteség , a batch , és . Teljes batch mean-loss:
gradiense
A négy tag: ; ; ; . Összegük , tehát
Most , : , . A mikro-batch gradiensek:
Az átlaguk , ami pontosan a teljes batch gradiens. A példában nincs BN vagy dropout, ezért a tétel alkalmazható.
Az alábbi kód a f32 CPU-főutat ellenőrzi. A kézirat írásakor nem futtattuk; a kimenet a hallgató helyi futtatásának tárgya, az egyezés a tételből következik.
# Futtatás: cd lab && python n095_accum_check.py
import torch
import torch.nn as nn
torch.manual_seed(20261020)
device = torch.device("cpu")
dtype = torch.float32
class TinyMLP(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.Sequential(
nn.Flatten(),
nn.Linear(24 * 24, 16),
nn.ReLU(),
nn.Linear(16, 3),
)
def forward(self, x):
return self.layers(x)
def flattened_grad(model):
return torch.cat([p.grad.reshape(-1).clone()
for p in model.parameters()])
model = TinyMLP().to(device)
criterion = nn.CrossEntropyLoss() # mean reduction, int64 target
x = torch.rand(16, 1, 24, 24, device=device, dtype=dtype)
y = torch.randint(0, 3, (16,), device=device)
# Referencia: teljes n=16 batch
model.zero_grad()
loss_full = criterion(model(x), y)
loss_full.backward()
g_ref = flattened_grad(model)
print("reference grad norm:", g_ref.norm().item())
# Akkumuláció: A=4, m=4, n=16
model.zero_grad()
A = 4
m = 4
for a in range(A):
xb = x[a * m:(a + 1) * m]
yb = y[a * m:(a + 1) * m]
loss_a = criterion(model(xb), yb)
(loss_a / A).backward() # a mikro-batch mean loss osztva A-val
g_acc = flattened_grad(model)
print("accumulated grad norm:", g_acc.norm().item())
print("max abs diff:", (g_ref - g_acc).abs().max().item())A loss_a / A osztás azért szükséges, mert a
CrossEntropyLoss mean-redukciójú: minden mikro-batch loss a
saját
elemű átlaga. Az
darab ilyen átlag összege
-szorosa
a kívánt
elemű átlagnak, ezért az
-val
osztás állítja helyre a nagy batch mean-loss skáláját. Mivel a modellben
nincs BN vagy dropout, a várt maximális abszolút eltérés a lebegőpontos
kerekítés szintjén van; float32 CPU-n az
allclose(atol=1e-6, rtol=1e-5) ellenőrzés észszerű.
A GPU-s AMP-terv jelölt, nem futtatott vázlata, a 2.14-es felület szerinti formában:
# CUDA-környezetben önállóan futtatandó időmérési vázlat.
scaler = torch.amp.GradScaler(device="cuda")
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
for xb, yb in loader:
optimizer.zero_grad(set_to_none=True)
with torch.amp.autocast(device_type="cuda", dtype=torch.float16):
logits = model(xb)
loss = criterion(logits, yb)
scaler.scale(loss).backward()
scaler.unscale_(optimizer)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
scaler.step(optimizer)
scaler.update()A sorrend indoklása: az unscale_ után a gradiens
float32-ben van, ezért a clipping a valódi
küszöböt látja; ha a clippinget az unscale_ előtt hívnánk,
a küszöb a skálázott gradiensre vonatkozna. A scaler.step
csak akkor frissít, ha a gradiensben nincs inf/NaN; a
scaler.update ezután igazítja a skálát. CPU-n bfloat16
autocast esetén a GradScaler indoklása gyengébb, mert a bfloat16
kitevőtartománya a float32-éhez hasonló; a támogatottságot a helyi
környezetben ellenőrizni kell.
Tévedés: „Azonos effektív batchméret mellett az akkumuláció ugyanazt a tréninget adja, mint a nagy batch.”
Ellenpélda. Tekintsünk egy
Linear → BatchNorm → ReLU → Linear modellt. A tétel (F2)
feltétele sérül: a BatchNorm train módban a mikro-batch statisztikáját
használja. A fenti négyelemű
példában a nagy batch átlaga
,
az első mikro-batch átlaga
;
a normalizált bemenetek és így a gradiens is különbözik. Dropout esetén
az RNG-hívások száma és sorrendje tér el. A két tréning ezért még azonos
seed mellett is szétválhat; az effektív batchméret egyenlősége
szükséges, de nem elégséges feltétel.
F1 (fogalmi, nehézség 1). Magyarázza el, miért lehet probléma egy nagyságú gradiens float16-ban, és hogyan viszonyul ehhez a GradScaler. Adjon konkrét értéket, amellyel a gradiens a normált float16 tartományba kerül.
F2 (levezetési, nehézség 2). Legyen , mérete , mérete . Mutassa meg általános vektorokkal, hogy általában, és adja meg a korrigált súlyozást, amely pontosan a nagy batch mean-gradiensét adja.
F3 (implementációs, nehézség 2). Írjon
accumulate_gradients(model, x, y, A) függvényt, amely a
fenti CPU-példa adatain (16 kép,
,
)
felhalmozza a gradienst, és adja vissza a referencia-gradienstől való
maximális abszolút eltérést. A megoldásnak tartalmaznia kell az
importot, seedet, dtype-ot, eszközt és egy allclose
ellenőrzést.
F4 (hibakeresési, nehézség 2). Az alábbi kódrészlet „gradiensakkumulációnak” nevezi magát:
for micro_x, micro_y in loader:
loss = criterion(model(micro_x), micro_y)
loss.backward()
optimizer.step()
optimizer.zero_grad()Mi a hiba? Milyen következménye van a paramétertrajektóriára, és hogyan javítaná úgy, hogy a hatékony batchméret legyen?
F5 (kísérleti/transzfer, nehézség 3). Tervezzen AMP-kísérletet egy helyi GPU-n vagy bfloat16-képes CPU-n a GlyphLab CNN-en. Adja meg a kérdést, a f32 baseline-t, a változó tényezőt, a választási metrikát, a seed- és környezetkezelést, valamint azt a következtetési határt, amelyet a kísérlet nem léphet át.
F1. A float16 legkisebb pozitív normált értéke
;
a
gradiens ennél kisebb, tehát a subnormal tartományba kerül, ahol a
mantissza vezető bitjei elvesznek, vagy nullára kerekül. A GradScaler a
loss-t és ezzel a visszaterjedt gradienst
-szeresére
nagyítja.
esetén
,
ami a normált float16 tartomány belsejében van; az unscale_
a frissítés előtt visszaoszt. A skálázás nem változtat a gradiens
irányán, csak az ábrázolhatóságát javítja.
F2. Jelölje . Ekkor
míg a nagy batch gradiens
A tag együtthatója a ellenében, a -é a ellenében; általában tehát nem egyenlők. A korrigált súlyozás : itt , ami pontosan a nagy batch gradiens. Szélsőeset: , esetén a korrigált súly -re 1, ami triviálisan helyes.
F3. Egy lehetséges megoldás:
import torch
import torch.nn as nn
torch.manual_seed(20261020)
device = torch.device("cpu")
dtype = torch.float32
class TinyMLP(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.Sequential(
nn.Flatten(), nn.Linear(24 * 24, 16),
nn.ReLU(), nn.Linear(16, 3),
)
def forward(self, x):
return self.layers(x)
def flat_grad(model):
return torch.cat([p.grad.reshape(-1).clone()
for p in model.parameters()])
def accumulate_gradients(model, x, y, A):
model.zero_grad()
n = x.shape[0]
m = n // A
criterion = nn.CrossEntropyLoss()
for a in range(A):
xb = x[a * m:(a + 1) * m]
yb = y[a * m:(a + 1) * m]
loss = criterion(model(xb), yb)
(loss / A).backward()
return flat_grad(model)
model = TinyMLP().to(device)
x = torch.rand(16, 1, 24, 24, device=device, dtype=dtype)
y = torch.randint(0, 3, (16,), device=device)
g_acc = accumulate_gradients(model, x, y, A=4)
model.zero_grad()
criterion = nn.CrossEntropyLoss()
criterion(model(x), y).backward()
g_ref = flat_grad(model)
diff = (g_ref - g_acc).abs().max().item()
print("max abs diff:", diff)
assert torch.allclose(g_ref, g_acc, atol=1e-6, rtol=1e-5)Alakellenőrzés: g_ref és g_acc dimenziója a
paraméterek számának összege, itt
.
A ReLU ugyanazon a bemeneti ponton azonos előjelet ad,
ezért a nem-sima pont nem érinti az egyezést.
F4. A hiba: a step() és
zero_grad() minden mikro-batch után lefut, tehát ez nem
akkumuláció, hanem
darab egymás utáni kis batch-es frissítés. A paramétertrajektória eltér
a nagy batch-es trajektóriától; BN-nél és dropoutnál a forward is eltérő
statisztikát/maszkot lát, tehát még a gradiens sem a nagy batch
gradiense. Javítás: a backward()-ot minden mikro-batch-re
meghívjuk, de a step() és zero_grad() csak a
ciklus után fut le, és a loss-t
-val
osztjuk (vagy a felhalmozott gradienst osztjuk a frissítés előtt). Ha a
clippinget használunk, azt a teljes,
-val
osztott gradiensre kell alkalmazni.
F5. Példa protokoll. Kérdés: az AMP megőrzi-e a záró test pontosságot a f32 kontrollhoz képest a GlyphLab CNN-en? Baseline: f32 SGD, ugyanaz a seed (pl. 20261020), tanítási séma, batchméret és early stopping a val loss alapján. Változó tényező: AMP be/ki. Metrika: a legjobb val-checkpoint test accuracy-ja; numerikus kontrollként az első lépés gradiensnorma-eltérése. Split: a rögzített scene-szintű train/val/test; a korrupt tesztet csak a végén értékeljük. Környezet: ugyanaz a gép, PyTorch-verzió rögzítve, GPU esetén szinkronizált mérés 24.2. CPU/GPU profiling és helyes időmérés szerint. Következtetési határ: a GlyphLab kicsi és mesterséges; az eredmény nem általánosítható természetes képi benchmarkra vagy más hardverre, és a seed-szórás (23.3. Páros összehasonlítás és csoportszintű intervallum) miatt egyszeri futás nem bizonyít fölényt.
Ellenőrizze, hogy (1) a clippinget az unscale_ után
hívja-e; (2) az akkumulációhoz a mikro-batch loss-t osztja-e
-val;
(3) a modellben nincs-e BN/dropout, mielőtt az egyezést állítja.
Emlékeztető 24.2. CPU/GPU profiling és helyes
időmérés alfejezetből: a GPU-időt csak szinkronizáció után mérjük,
és a memóriaméréshez a foglalás időpontját is rögzítjük. 23.3. Páros összehasonlítás és csoportszintű
intervallum szerint a seed-variáció és a páros teszt külön
bizonytalanság; 20.3. Preprocessing, domain shift és
súlylicenc szerint a preprocessing-szerződés az AMP-től függetlenül
érvényes; 12.3. Sanity check és kis mintára
túlillesztés tiny overfitje az akkumulációs hibák gyors szűrője.
A kevert pontosságú tanítás float32 master-súlyok mellett alacsonyabb pontosságú forward/backward operátorokat használ. A GradScaler a loss skálázásával akadályozza meg, hogy a float16 gradiens alulcsorduljon; a frissítés előtt a skálát visszaosztjuk. A gradiensakkumuláció azonos méretű mikro-batch-ek és batch-független modell esetén ekvivalens a nagy batch átlaggal: a mikro-batch mean-loss gradienseinek átlaga a teljes mean-loss gradiense. BatchNorm és dropout megtöri ezt az egyezést, mert a forward eredménye a batch összetételétől vagy a hívások közötti RNG-állapottól függ. A clipping az unscaling után a globális gradiennormára hat. A labor f32 kontrollt készít; mért AMP-eredményt csak tényleges futtatás után szabad közölni.
A 24.4. VI. ellenőrző pont: megbízható CNN-training VI. ellenőrző pont a teljes CNN-training megbízhatóságát kéri számon: az itteni akkumulációs és AMP-döntéseket a tanulási görbével, checkpointtal és záró teszttel együtt kell indokolni.
Felhasznált források: