A Tanítási ciklus és adatbetöltés tanítási ciklusában a kérdés az volt, hogyan számítunk ki egy gradienslépést. Most a teljes adat- és számítási út költségét mérjük, mielőtt gyorsítóra váltanánk. A döntési helyzet: a Northstar prediktív feladatnál 2400 rendelés és 8 rendeléskori feature áll rendelkezésre, a cél a 7 napot meghaladó kézbesítés valószínűségének becslése. A -as fp32 mátrix nagyjából byte, tehát nem a méret vagy a memória a szűk keresztmetszet, hanem a betöltés, a Python-keret overheadje és a fejlesztési idő.
Az előzetes becslésem ezért az, hogy ehhez a Northstar-mérethez a CPU is elegendő, és a GPU csak akkor éri meg, ha a tényleges mérés szerint a számítás vagy az adatmozgatás a domináns költség. Ahhoz, hogy a batch-, memória- és transferhatásokat értelmezni tudjuk, egy nagyobb, de helyben legyártható szintetikus mátrixon mérünk; a mért számok a saját gépedre vonatkoznak, nem a könyv előregyártott eredményére.
A batching (kötegelés) a DataLoader
egyik alapbeállítása: egyszerre
mintát ad át a modellnek. A minibatch gradiens
ahol a modell paramétere, a veszteség, pedig a méretű batch -edik mintája. Ha a gradiensek független, azonos eloszlású mintánkénti becslések, akkor a variancia az szabályt követi:
Nagyobb batch tehát kisebb gradienstorzítást, de több memóriát és kevesebb paraméterfrissítést jelent epochonként; az optimális mérési kérdés.
A transfer a CPU- és a GPU-memória közötti másolás.
A PyTorch tensor.to(device) hívása a háttérben
PCIe/átviteli sávszélességet használ. CPU-n nincs külön devicetransfer,
de a CSV/Parquet betöltés, a pandas átalakítás és a tensorré alakítás
ugyanúgy a teljes út része. GPU-mérésnél a transfert külön kell mérni:
torch.cuda.synchronize() után
time.perf_counter(), mert a GPU-műveletek aszinkronok
lehetnek.
A VRAM a GPU saját memóriája. Három összetevője van:
a paraméterek, a gradiensek és az optimizer állapota. Adamnél minden
fp32 paraméterhez tartozik gradiens, első momentum és második momentum,
tehát a paraméterbyte-ok négyszerese kell. Az aktivációk ezen felül
batchfüggő memóriát foglalnak. A
torch.cuda.memory_allocated() csak GPU-n hívható; CPU-s
becsléshez kézzel számolunk, ahogy a következő szakasz mutatja.
A mixed precision (vegyes pontosság) áttekintő
szinten annyit jelent, hogy a számítási részek fp16/bf16-ban futnak, a
súlyok viszont fp32 master másolatban maradnak. Ezzel csökken a memória-
és sávszélességigény, de az fp16 kis tartománya miatt a gradiens
alulcsordulhat. A torch.amp.GradScaler ezért a veszteséget
felskálázza a backward előtt, majd a súlyfrissítésnél visszaoszt. CPU-n
ez általában nem hoz automatikus előnyt; a használat előtt méréssel kell
igazolni az előnyt.
Az early stopping (korai leállás) a validációs
veszteséget nézi: ha patience epochon át nincs
min_delta-nál nagyobb javulás, a tanítást leállítjuk, és a
legjobb mentett súlyt töltjük vissza. A validációs halmaz itt
modellválasztási eszköz, a teszthalmazt csak a leállás után
értékelhetjük; ellenkező esetben a teszt vezérli a modellt.
A seed limits (magkorlátok) azt rögzítik, hogy a pszeudovéletlen sorozat ugyanazon gépen, verzióval és csomagkészlettel ismételhető legyen. A seed nem garantál platformok közötti bitazonosságot: a GPU-k atomi redukciói, a könyvtárverziók és a párhuzamos futtatás eltérést okozhatnak. Ezért különböztetjük meg a numerikus, statisztikai és környezeti reprodukálhatóságot.
A TensorBoard áttekintésben a
SummaryWriter naplózza a veszteség-, idő- és
memóriagörbéket; a tensorboard --logdir runs parancs
böngészőben jeleníti meg őket. A görbe munkanapló, nem helyettesíti a
mért hardver-, méret- és ismétlésadatokat tartalmazó jegyzőkönyvet.
A bemutatott modell: bemenet, rejtett neuron, kimenet. A paraméterszám
Egy lineáris réteg forward műveletszáma FLOP, mert egy szorzás-összeadás pár két lebegőpontos művelet. A két rétegre:
FLOP. A backward durván a forward kétszerese, így egy batch teljes költsége körülbelül . Például esetén egy batch körülbelül millió FLOP, a batchből álló epoch pedig körülbelül millió FLOP. Ez a műveletmennyiség egy modern CPU-n is néhány tizedmásodperces nagyságrendű lehet; a mérés dönti el, hogy a Python-overhead vagy a számítás dominál-e.
A memóriabecslés ugyanilyen egyszerű. A paraméter fp32-ben KiB; Adamnél a paraméter, gradiens, momentum és variancia együtt körülbelül KiB. A batch aktivációi: bemenet byte, rejtett kimenet byte, logit byte; a backward hasonló nagyságrendű, összesen fél megabyte alatt maradunk. Ezért ez a modell VRAM szempontból soha nem indokol GPU-t; a GPU-kérdést a műveletmennyiség, a transfer és a betöltés dönti el.
A gradiensvariancia-összefüggés visszautal a Power, mintanagyság és többszörös próbák becslési varianciájára: a batchméret növelése ugyanazt a zajcsökkentést adja, mint a mintanagyság növelése, de csak független minták esetén érvényes ilyen tisztán. A DataLoader shuffle után hozzávetőleg függetlennek tekinthető, de a modell frissítése közben az eloszlás már nem azonos; ezért a képlet az egy paraméterpont körüli lokális közelítés, nem globális ígéret.
A következő script a fenti modell teljes epochonkénti költségét méri CPU-n, különböző batchméretekkel. Az outputot a tanuló gépén kell létrehozni; az itt látható JSON-struktúra a várt séma, nem ellenőrzött mérési jegyzék. A kód a projektgyökérben futtatható.
import json
import time
from pathlib import Path
import torch
from torch import nn
from torch.utils.data import DataLoader, TensorDataset
SEED = 20261020
torch.manual_seed(SEED)
def make_data(n=8192, d=32):
X = torch.randn(n, d)
w = torch.randn(d, 1)
y = ((X @ w + 0.2 * torch.randn(n, 1)) > 0).float()
return X, y
def build_model(d):
return nn.Sequential(
nn.Linear(d, 48),
nn.ReLU(),
nn.Linear(48, 1),
)
X, y = make_data()
dataset = TensorDataset(X, y)
results = []
for batch_size in [32, 64, 128, 256, 512]:
# minden batchmérethez azonos kezdősúly
torch.manual_seed(SEED)
model = build_model(X.shape[1])
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.BCEWithLogitsLoss()
loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# egy batch melegítés, hogy az első allokáció ne torzítson
for xb, yb in loader:
optimizer.zero_grad()
loss_fn(model(xb), yb).backward()
optimizer.step()
break
n = 0
t0 = time.perf_counter()
for xb, yb in loader:
optimizer.zero_grad()
out = model(xb)
loss = loss_fn(out, yb)
loss.backward()
optimizer.step()
n += xb.shape[0]
dt = time.perf_counter() - t0
results.append({
"batch_size": batch_size,
"samples": n,
"epoch_s": round(dt, 4),
"samples_per_s": round(n / dt, 2) if dt > 0 else None,
})
path = Path("s111_cpu_results.json")
path.write_text(json.dumps(results, indent=2) + "\n")
print(json.dumps(results, indent=2))Futtatás Linux/macOS terminálból a projektgyökérből:
python s111_cpu_measure.pyWindows PowerShellből ugyanez a
python s111_cpu_measure.py parancs. A TensorBoard-napló
opcionálisan a mérés köré építhető:
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("runs/s111_cpu_measure")
for row in results:
writer.add_scalar("time/epoch_s", row["epoch_s"], row["batch_size"])
writer.add_scalar("throughput/samples_per_s", row["samples_per_s"], row["batch_size"])
writer.close()A tensorboard --logdir runs parancs a projektgyökérből
indítva nyitja meg a görbéket; a pontos elérhető verzió a telepített
csomagtól függ, ezért a saját környezetedben ellenőrizd.
Az eredményeket a saját gépeden mérd, legalább három ismétléssel, és
a mediánt jegyezd fel. Minden futásra két invariantet ellenőrizz:
samples értéke mindig
,
és samples_per_s értéke samples / epoch_s
kerekítve. A tipikus CPU-mintázat az, hogy a kisebb batch nagyobb
Python- és kernelindítási overheaddel jár, ezért a nagyobb batch növeli
az átviteli sebességet addig, amíg a cache vagy a háttérterhelés újabb
korlátot nem szab. A pontos szám géptől függ; csak a saját futtatásodat
nevezd mértnek.
| Mutató | Jelentés | Ellenőrzés |
|---|---|---|
| batch_size | minibatch méret | input |
| samples | feldolgozott sorok | mindig 8192 |
| epoch_s | epoch idő | tanulói mérés |
| samples_per_s | átviteli sebesség | samples / epoch_s |
A GPU-döntési lap akkor használható, ha CUDA elérhető; enélkül a terv elméleti marad, és tilos gyorsulást közölni. A lap lépései:
torch.cuda.is_available(), torch.version.cuda,
GPU neve.x.to("cuda"); torch.cuda.synchronize(); dt.A mixed precision használatakor a veszteségfelskálázás kötelező GPU-n:
use_cuda = torch.cuda.is_available()
device = torch.device("cuda" if use_cuda else "cpu")
model.to(device)
scaler = torch.amp.GradScaler("cuda", enabled=use_cuda) if use_cuda else None
optimizer.zero_grad()
if scaler is not None:
with torch.amp.autocast("cuda", dtype=torch.float16, enabled=True):
out = model(xb.to(device))
loss = loss_fn(out, yb.to(device))
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
else:
loss = loss_fn(model(xb), yb)
loss.backward()
optimizer.step()A torch.amp API a PyTorch 2.x felület; a régi
torch.cuda.amp elavult, ezért a saját verziód
dokumentációját ellenőrizd. Az early stopping vázlata ugyanebben a
környezetben:
best_loss = float("inf")
patience = 5
min_delta = 1e-4
no_improve = 0
for epoch in range(max_epochs):
train_loss = train_one_epoch(model, train_loader, device)
val_loss = evaluate(model, val_loader, device)
writer.add_scalar("loss/train", train_loss, epoch)
writer.add_scalar("loss/val", val_loss, epoch)
if val_loss < best_loss - min_delta:
best_loss = val_loss
no_improve = 0
torch.save(model.state_dict(), "best_model.pt")
else:
no_improve += 1
if no_improve >= patience:
breakA train_one_epoch és evaluate a Tanítási ciklus és adatbetöltés alfejezetből ismert
train/eval módot és
torch.no_grad()-ot használja; a max_epochs
tetszőleges felső korlát, a patience a validációs javulás
türelmi ideje. A végső teszthalmazt csak a best_model.pt
betöltése után szabad egyszer kiértékelni.
CPU–GPU gyorsításnál a teljes mérési szakaszt és adatmozgatást is vegyük figyelembe; aszinkron GPU-futtatás időzítésénél szinkronizálás szükséges.
Számítsd ki a bemutatott modellre: paraméterszámot, egy batch lineáris forward FLOP-számát, a forward+backward közelítő FLOP-számát, továbbá a és minibatch gradiens varianciájának arányát független, azonos eloszlású gradiensek feltételével.
Módosítsd a s111_cpu_measure.py scriptet úgy, hogy
minden batchmérethez három ismétlésben futtassa a mérést, a kimenetbe
írja be a három epoch_s értéket és a mediánt, és mentse el
s111_cpu_results.json néven. Futtasd a saját gépeden, és
ellenőrizd, hogy minden samples mező
.
Valaki a mixed precision kódot így írta, és a tanítási veszteség
néhány lépés után nan lett:
with torch.amp.autocast("cuda", dtype=torch.float16):
out = model(xb.to(device))
loss = loss_fn(out, yb.to(device))
loss.backward()
optimizer.step()Magyarázd meg, mi a hiba, és add meg a javítást. Adj egy második lehetséges okot is arra, ha CPU-mérésben a epoch lassabb, mint a .
Tegyük fel, hogy sor, feature és batch adott. A modell legyen . Készíts GPU-s becslési tervet: számítsd ki az input tenzor byte-méretét, a modell paraméterszámát, az Adam fp32 memóriafelső korlátját, a batch aktivációinak közelítő méretét, és egy átviteli időbecslést GB/s effektív sávszélességgel. Mit kell ténylegesen megmérned, mielőtt gyorsulást állítanál?
. A forward lineáris költsége FLOP. A forward+backward közelítőleg ennek háromszorosa: FLOP. A varianciaarány:
A szórásarány ennek négyzetgyöke, ; a nagyobb batch tehát felére csökkenti a gradienszaj szórását ebben az idealizált közelítésben.
A lényeg a mérési hurok megkettőzése: minden batchmérethez futtass
egy belső ciklust [0, 1, 2] ismétléssel, gyűjtsd listába a
dt értékeket, és a mediánt a statistics.median
függvénnyel számold. Az újraindított
torch.manual_seed(SEED) maradjon a külső ciklus elején,
hogy azonos kezdősúlyokkal induljon minden batchméret. A kimenet egy
lehetséges bővített rekordja:
{
"batch_size": 128,
"samples": 8192,
"epoch_s_runs": [0.0411, 0.0398, 0.0403],
"epoch_s_median": 0.0403,
"samples_per_s": 203323.1
}Itt az epoch_s_runs a három mért idő, a
samples_per_s a mediánból számított
.
Az samples invariant minden sorban
;
ha nem, akkor a DataLoader vagy a batchméretezés hibás. A fenti konkrét
szám csak szemléltető, nem mért; a saját gép és a háttérterhelés eltérő
értéket adhat.
A nan oka a GradScaler hiánya: fp16 autocast alatt a kis
gradiensek alulcsordulhatnak, a backward így nem véges értéket ad. A
javítás:
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()Ha a scaler nem elérhető, maradjon fp32 CPU-n, vagy kapcsold ki az autocastot. A második lehetséges ok, hogy a lassabb CPU-n: a nagyobb batch kinőhet a cache-ből, a gép háttérfolyamatot futtat, vagy egyetlen mérésből vontál le következtetést. A helyes válasz mérési terv: legalább öt ismétlés, melegítés, a medián használata, és a háttérterhelés rögzítése a jegyzőkönyvben.
Az input tenzor mérete GB, tehát nem fér be egyetlen batchbe; a betöltés így streaminget igényel. A modell paraméterszáma . Az Adam fp32 felső korlát byte KiB. A batch aktivációi: bemenet byte, rejtett kimenet byte, logit byte, backward hasonló; a teljes aktiváció MB alatt marad. A GB/s effektív sávszélességgel számolt átvitel s. Ez elméleti becslés; a valós sávszélesség hardverfüggő. Mielőtt gyorsulást állítanál, mérd meg a teljes utat legalább háromszor: betöltés, CPU→GPU transfer, train, eval; rögzítsd a GPU nevét, verzióját és a PCIe-környezetet. A s-on felül a betöltés gyakran dominánsabb, ezért a GPU-váltás csak akkor indokolt, ha a teljes mért futásidő és a projektcél is igazolja.
A leggyakoribb tévedés, hogy a GPU-t automatikusan gyorsabbnak tekintjük. A Northstar -as mátrixa KB; a bemutatott rétegekkel a teljes epoch körülbelül millió FLOP alatt marad. Ez a műveletmennyiség a kernelindítás, a driver ütemezés és az adatmozgatás tartományába esik: a GPU-s overhead nagyobb lehet, mint a CPU-s teljes számítás, ezért a „modern GPU” látszólagos előnye itt veszteséggé válhat. A döntés sohasem eszköz, hanem a teljes adat- és számítási út mért költsége alapján születik.
A gradiensvariancia-összefüggés visszautal a Power, mintanagyság és többszörös próbák mintanagysági becslésére: a batch növelése statisztikai zajt csökkent, de nem tesz csodát a ritka célváltozóval. A LLM mint sztochasztikus komponens latency-keretére emlékeztet, hogy az LLM-értékelésben is a teljes késleltetés és költség döntött, nem a modell mérete önmagában. Az Anomália és ritka esemény felügyelet nélkül pedig arra figyelmeztet, hogy a ritka esemény nem gyógyítható gyorsabb mátrixszorzással; a rangsor és az emberi validálás akkor is kell, ha a GPU hirtelen elérhetővé válik.
Önellenőrzés: fel tudod-e sorolni a teljes út lépéseit és mértékegységeit; ki tudod-e számolni az Adam-memória felső korlátját; meg tudod-e mondani, mit garantál a seed és mit nem; el tudod-e különíteni a betöltés, transfer, train és eval idejét a saját mérésedben? Ha bármelyik nem egyértelmű, futtasd újra a feladatok 2. és 4. mérését, mielőtt gyorsító mellett döntenél.
A gyorsítóválasztás előtt a teljes adat- és számítási út mért költsége dönt: betöltés, tensor-konverzió, batch, transfer, forward/backward, validáció és kiértékelés együtt. A batchméret a gradiensvarianciát -vel csökkenti, de a memóriát növeli. A VRAM-ot paraméter, gradiens, Adam-állapot és aktivációk adják; kis modellnél ez nem korlát. A mixed precision csökkentheti a memóriát, de GradScaler kell hozzá. Az early stopping a validációs görbét modellválasztásra használja, a seed pedig csak azonos környezetben ad ismételhetőséget. A következő lépés a VII. ellenőrző pont, ahol a PyTorch-kísérletnek a mérési jegyzőkönyvvel együtt kell megállnia a szivárgásmentes értékelés próbáját.