Inleiding
Grotere diep leer modellen hebben meer rekenkracht en geheugen nodig. Door de ontwikkeling van nieuwe technieken is de training van diep neuraal netwerken sneller gerealiseerd. In plaats van FP32 (volledige nauwkeurigheid floating-point getalsformat), kun je FP16 (halve nauwkeurigheid floating-point getalsformat) gebruiken, en onderzoekers hebben ontdekt dat het gebruik ervan samen beter is.
Gemengde nauwkeurigheid staat toe om te trainen met halve nauwkeurigheid terwijl nog veel van de nauwkeurigheid van het enkele voorgedefinieerd netwerk wordt behouden. Het begrip “gemengde nauwkeurigheidstechniek” verwijst naar het feit dat deze methode zowel enkele als halve nauwkeurigheid gebruikt.
In deze overzicht van de Automatische Gemengde Precisie (AMP) training met PyTorch, laten we zien hoe de techniek werkt door de stappen van het gebruik van AMP door te geven, en bespreken we meer geavanceerde toepassingen van AMP-technieken met code schalen voor gebruikers om later te integreren met hun eigen code.
Vereisten
Basis Kennis van PyTorch: Familiariteit met PyTorch, inclusief zijn kernconcepten als tensoren, modules en de trainingslus.
Inzicht in de Fundamentele beginselen van Deep Learning: Concepten zoals neurale netwerken, backpropagation en optimalisatie.
Kennis van Gemengde Precisie Training: Bewustzijn van de voordelen en nadelen van gemengde precisietraining, inclusief verminderde geheugengebruik en snellere berekeningen.
Toegang tot Compatibele Hardware: Een GPU die gemengde precisie ondersteund, zoals NVIDIA-GPU’s met Tensor Cores (bijvoorbeeld, architecturen van Volta, Turing, Ampere).
Python en CUDA-instellingen: Een functionele Python-omgeving met PyTorch geïnstalleerd en CUDA geconfigureerd voor GPU-acceleratie.
Overzicht van Gemengde Precisie
Net als de meeste diepgeleerde frameworks, traint PyTorch standaard op 32-bit floating-point data (FP32). FP32 is echter niet altijd nodig voor succes. Het is mogelijk om 16-bit floating-point te gebruiken voor enkele berekeningen waar FP32 meer tijd en geheugen verbruikt.
Hiermee ontwikkelden NVIDIA-engineers een techniek die gemengde precisietraining toestaat in FP32 voor een klein aantal berekeningen terwijl de meeste netwerken in FP16 draaien.
- Converteer het model zodat het de float16-datatype gebruikt waar mogelijk.
- Houd float32-hoofdgewichten om elke iteratie gewichtsveranderingen te accumuleren.
- Het gebruik van verliesscaling om kleine gradientenwaarden te behouden.
Mixed-Precisie in PyTorch
Voor gemengde precisie training, biedt PyTorch al veel ingebouwde functionaliteiten.
Een module-parameter wordt omgezet naar FP16 wanneer u de .half()
methode aanroep, en de gegevens van een tensor worden omgezet naar FP16 wanneer u .half()
aanroept. snelheidsoptimaliseerde FP16 rekeninghouding wordt gebruikt om elke berekening uit te voeren op deze modules of tensor. NVIDIA wiskundige bibliotheken (cuBLAS en cuDNN) worden goed ondersteund door PyTorch. Gegevens van de FP16-pijplijn worden behandeld met Tensor Cores om GEMMs en convoluties uit te voeren. Om Tensor Cores in cuBLAS te gebruiken, moeten de dimensies van een GEMM ([M, K] x [K, N] -> [M, N]) veelvouden zijn van 8.
Introduceren Apex
Apex’s gemengde precisie hulpmiddelen zijn bedoeld om de trainingssnelheid te verhogen terwijl de nauwkeurigheid en stabiliteit van single-precision training behouden worden. Apex kan berekeningen uitvoeren in FP16 of FP32, automatisch de hoofdparameterconversie verwerken en automatisch verliezen schalen.
Apex is ontworpen om het gemakkelijker voor onderzoekspersonen te maken om gemengde precisie training in hun modellen in te sluiten. Amp, afkorting van Automatic Mixed-Precision, is een van de functionaliteiten van Apex, een lichtgewicht PyTorch extensie. Enkele extra regels op hun netwerken zijn voldoende voor gebruikers om de voordelen van gemengde precisietraining te genieten met Amp. Apex werd gelanceerd op CVPR 2018, en het is opmerkelijk dat de PyTorch community al sinds de release stevige steun heeft gegeven aan Apex.
Door slechts kleine aanpassingen te brengen aan het lopende model, maakt Amp het zo dat u niet hoeft te worstelen met gemengde typen bij het maken of uitvoeren van uw script. De aannames van Amp passen misschien niet zo goed bij modellen die PyTorch in minder gebruikelijke manieren gebruiken, maar er zijn haakjes om deze aannames op de nodige wijze aan te passen.
Amp biedt alle voordelen van gemengde precisietraining zonder dat er verlies schaling of typeconversies moeten worden expliciet beheerd. Het GitHub-website van Apex bevat instructies voor de installatieproces, en de officiële API documentatie kan hier gevonden worden.
Hoe Amps werken
Amp gebruikt een whitelist/blacklist Paradijs op het logische niveau. Tensorbewerkingen in PyTorch omvatten neurale netwerkfuncties zoals torch.nn.functional.conv2d, eenvoudige wiskundige functies zoals torch.log en tensormethodes zoals torch.Tensor. add__. Er zijn drie hoofd categorieën van functies in dit universum:
- Whitelist: Functies die kunnen profiteren van de snelheid van FP16 wiskunde. Gewone toepassingen omvatten matrixvermenigvuldigen en convolutie.
- Blacklist: Invoer moet zijn in FP32 voor functies waarvoor 16 bits nauwkeurigheid wellicht niet genoeg is.
- Alles anders (alle functies die overblijven): Functies die kunnen draaien in FP16, maar de kosten van een FP32 -> FP16 cast om ze in FP16 uit te voeren zijn niet waardoor het verschil in snelheid niet significant is.
Het takenveld van Amp is eenvoudig, minstens in theorie. Amp bepaalt of een PyTorch-functie op de whitelist staat, op de blacklist of geen van beide alvorens hem aan te roepen. Alle argumenten moeten worden geconverteerd naar FP16 als ze op de whitelist staan of FP32 als ze op de blacklist staan. Als geen van beide, moet u alleen ervoor zorgen dat alle argumenten van hetzelfde type zijn. Deze beleid is niet even gemakkelijk toe te passen in de praktijk als het lijkt.
Gebruik van Amp in Conjunction With een PyTorch Model
Om Amp te integreren in een bestaand PyTorch-script, volg deze stappen:
- Gebruik de Apex-bibliotheek om Amp te importeren.
- Initialiseer Amp zodat het de vereiste wijzigingen aan het model, de optimizer en de PyTorch-interne functies kan aanbrengen.
- Merk waar de backpropagation (.backward()) plaatsvindt zodat Amp de verliezen tegelijkertijd kan schalen en de per-iteratie-status kan opschonen.
Stap 1
Er is maar één regel code voor de eerste stap:
Stap 2
Het neurale netwerkmodel en de optimizer die worden gebruikt voor training moeten al zijn gespecificeerd om deze stap te voltooien, die uit één regel bestaat.
Extra instellingen staan u toe om Amp’s tensor- en operationstype aanpassingen af te fine-tunen. De functie amp.initialize() accepteert veel parameters, waarvan we er maar drie specificeren:
- models (torch.nn.Module of lijst van torch.nn.Modules) – Modellen om aan te passen/te casten.
- optimalisatoren (optioneel, torch.optim.Optimizer of lijst van torch.optim.Optimizers) – Optimalisatoren om te wijzigen/te casten. VERPLICHT voor training, optioneel voor inferentie.
- opt_level (str, optioneel, standaard=”O1″) – Pure of gemengde precisieoptimalisatieniveau. Geaccepteerde waarden zijn “O0”, “O1”, “O2” en “O3”, die in detail hierboven uitgelegd zijn. Er zijn vier optimalisatieniveaus:
O0 voor FP32 training: Dit is een no-op. U hoeft zich niet te bekommeren over dit, aangezien uw inkomende model al FP32 moet zijn, en O0 kan helpen om een basis voor nauwkeurigheid te vestigen.
O1 voor Gemengde Precisie (aanbevolen voor typische toepassingen): Verander alle Tensor en Torch methodes om een whitelist-blacklist input casting schema te gebruiken. In FP16 worden whitelist bewerkingen zoals Tensor Core-vriendelijke bewerkingen als GEMMs en convoluties uitgevoerd. Softmax is bijvoorbeeld een blacklist bewerking die FP32 nauwkeurigheid vereist. Tenzij anders uitdrukkelijk opgegeven, gebruikt O1 ook dynamische verliesscaling.
O2 voor “Almost FP16” Gemengde Precisie: O2 casteert de modelweights naar FP16, repareert de model voorwaartsmethode om inputgegevens te casten naar FP16, houdt batchnorms in FP32, behoudt FP32 hoofdwegen,更新 optimalisator aan de parametergroepen zodat de optimizer.step() direct op de FP32 wegen actie neemt, en implementeert dynamische verliesscaling (tenzij overschreden). In tegenstelling tot O1 patch de O2 geen Torch functies of Tensor methodes.
O3 voor FP16 training: O3 kan niet zo stabiel zijn als O1 en O2 betreffende ware gemengde precisie. Daarom kan het voordelig zijn om een basis snelheid in te stellen voor uw model, waarvan de efficiëntie van O1 en O2 kan worden beoordeeld.
De extra eigenschap overschrijving keep_batchnorm_fp32=True in O3 kan helpen u de “lichtsnelheid” vast te stellen als uw model batch normalisatie gebruikt, waardoor cudnn batchnorm mogelijk wordt.
O0 en O3 zijn geen ware gemengde precisie, maar ze helpen respectievelijk accuracy en snelheidsbasislijnen in te stellen. Een gemengde precisieimplementatie wordt gedefinieerd als O1 en O2.
U kunt beide proberen en zie welke het beste performantie en nauwkeurigheid verbeterd voor uw specifieke model.
Stap 3
Zorg er voor dat u identificeert waar de achterwaartse passage plaatsvindt in uw code.
Enkele regels code die er als volgt uitzien zullen verschijnen:
loss = criterion(…)
loss.backward()
optimizer.step()
Stap 4
Met behulp van de Amp context manager kunt u door middel van een envelop om de achterwaartse passage loss schaling inschakelen:
loss = criterion(…)
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
optimizer.step()
Dat is alles. U kunt nu uw script met gemengde precisietraining opnieuw uitvoeren.
Functieoproepen vastleggen
PyTorch ontbreekt een statische modelobject of graaf om aan te hangen en de boven genoemde kasten in te sluiten, omdat het zo flexibel en dynamisch is. Door de benodigde functies te “monkey patchen” kan Amp dynamischParameters intercepten en kasten.
Bijvoorbeeld, u kunt het onderstaande code gebruiken om er voor te zorgen dat de argumenten voor de methode torch.nn.functional.linear altijd naar fp16 worden gecast:
orig_linear = torch.nn.functional.linear
def wrapped_linear(*args):
casted_args = []
for arg in args:
if torch.is_tensor(arg) and torch.is_floating_point(arg):
casted_args.append(torch.cast(arg, torch.float16))
else:
casted_args.append(arg)
return orig_linear(*casted_args)
torch.nn.functional.linear = wrapped_linear
Alhoewel Amp mogelijk verbeteringen toevoegt om de code sterker te maken, zorgt het aanroepen van Amp.init() effectief ervoor dat aappatches worden ingevoegd in alle relevante PyTorch-functies zodat argumenten op runtime correct worden gecast.
Minimaliseren Kasten
Elke spoor wordt slechts eenmaal per iteratie FP32 -> FP16 gecast sinds Amp een interne cache beheert van alle parameterkasten en ze teruggebruikt als nodig. Bij elke iteratie markeert de contextmanager voor het achterwaarts traject Amp om de cache op te schonen.
Autokasting en Gradient Schaling Met PyTorch
“Automatisch gemengd nauwkeurigheidstraining” duidt op de combinatie van torch.cuda.amp.autocast en torch.cuda.amp.GradScaler. Door torch.cuda.amp.autocast te gebruiken, kun je autocasting alleen in bepaalde gebieden instellen. Autocasting kiest automatisch de nauwkeurigheid voor GPU-bewerkingen om de efficiëntie te optimaliseren terwijl de nauwkeurigheid behouden blijft.
De torch.cuda.amp.GradScaler-objecten maken het gemakkelijker om de gradienten schaleringstappen uit te voeren. Gradientenschalering helpt onderflow van gradienten te verminderen, wat helpt netwerken met float16-gradienten beter tot convergentie te komen.
Hieronder staat code om te demonstreren hoe je autocast() kunt gebruiken om gemengde nauwkeurigheid in PyTorch te krijgen:
# Maakt model en optimer in standaard nauwkeurigheid
model = Net().cuda()
optimizer = optim.SGD(model.parameters(), ...)
# Maakt een GradScaler eenmaal aan het begin van het trainen.
scaler = GradScaler()
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
# Voert de voorwaartse passage uit met autocasting.
with autocast(device_type='cuda', dtype=torch.float16):
output = model(input)
loss = loss_fn(output, target)
# Achterwaartse bewerkingen worden uitgevoerd in dezelfde datatype die autocast voor de corresponderende voorwaartse bewerkingen kiest.
scaler.scale(loss).backward()
# scaler.step() deprimeert eerst de gradienten van de parameters die aan de optimer zijn toegewezen.
scaler.step(optimizer)
# Update de schaal voor de volgende iteratie.
scaler.update()
Als de voorwaartse passering voor een specifieke bewerking uitgaat van float16-ingevoerde gegevens, dan levert de terugwaartse passering van deze bewerking float16-afnamecoëfficients op, en float16 kan mogelijk geen afnamecoëfficients voor kleine magnitude’s uitdrukken.
Wanneer deze waarden naar nul worden afgevlusht (“onderflow”), zal de update voor de betreffende parameters verloren gaan.
Gradient schaling is een techniek die gebruikmaakt van een schalingsfactor om de verliezen van het netwerk te vermenigvuldigen en vervolgens de terugwaartse passering uit te voeren op de gescaleerde verliezen om onderflow te voorkomen. Het is ook nodig om de gradiënten die door het netwerk stromen te schalen met dezelfde factor. Hierdoor worden de gradiënten uitgebreid, wat hen ervoor beschermt te worden afgevlusht naar nul.
Voordat parameters worden bijgewerkt, moet de afnamecoëfficient van elke parameter (.grad eigenschap) worden ongedeeld zodat de schaalfactor geen invloed heeft op de leeringssnelheid. Zowel autocast als GradScaler kunnen afzonderlijk worden gebruikt omdat ze modulair zijn.
Werken met Ongedeelde Gradiënten
Gradiënt knippering
We kunnen alle gradiënten schalen door middel van de methode Scaler.scale(Loss).backward()
. De .grad
eigenschappen van de parameters tussen backward()
en scaler.step(optimizer)
moeten worden ongedeeld voordat u ze wijzigt of inspecteert. Als u de globale norm (zie torch.nn.utils.clip_grad_norm_()) of de maximale grootte (zie torch.nn.utils.clip_grad_value_()) van uw gradiëntset wilt beperken tot een bepaald waarden (een door de gebruiker opgelegd drempelwaarde), kunt u een techniek gebruiken die “gradiënt clipping” heet.
Clipmen zonder degradering zou ervoor zorgen dat de norm/maximale grootte van de gradiënten worden gescaald, waardoor uw gevraagde drempelwaarde ongeldig wordt gemaakt (die bedoeld was voor de niet gescaalde gradiënten). Gradiënten die ingevoerd zijn door de gegeven parameters van de optimizer worden ongedeeld door scaler.unscale (optimizer).
U kunt de gradiënten van andere parameters die eerder aan een andere optimizer (zoals optimizer1) zijn gegeven ongedeeld maken door gebruik te maken van scaler.unscale (optimizer1). We kunnen dit concept illustreren door twee regels code toe te voegen:
# Ongedeeld de gradiënten van de door optimizer toegewezen parameters in-place
scaler.unscale_(optimizer)
# Aangezien de gradiënten van de door optimizer toegewezen parameters zijn ongedeeld, worden ze gewoon geklipt:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)
Werken met gematigde gradiënten
Gradiënt accumulatie
Gradiënt accumulatie is gebaseerd op een onverstandig eenvoudige concept. In plaats van de modelparameters bij te werken, wacht het en accumuleert de gradiënten over succesvolle batches om verlies en gradiënt te berekenen.
Na een bepaald aantal batches worden de parameters bijgewerkt afhankelijk van de accumuleerde gradiënt. Hieronder een stukje code hoe je gradiënt accumulatie gebruikt met pytorch:
scaler = GradScaler()
for epoch in epochs:
for i, (input, target) in enumerate(data):
with autocast():
output = model(input)
loss = loss_fn(output, target)
# normaliseer de verliezen
loss = loss / iters_to_accumulate
# Accumuleert gematigde gradiënten.
scaler.scale(loss).backward()
# gewichten update
if (i + 1) % iters_to_accumulate == 0:
# may unscale_ hier als gewenst
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad()
- Gradiënt accumulatie voegt gradiënten toe over een adequate batchgrootte van batch_ per_iter * iters_to_accumulate.
De schaal moet gecalibreerd worden voor de effectieve batch; dit betekend controleren op inf/NaN cijfers, overslaan van stap als er enige inf/NaN worden ontdekt, en het bijwerken van de schaal tot de granulariteit van de effectieve batch.
Het is ook crucial om gradiënten in een gematigde en consistente schaalfactor te behouden bij het toevoegen van gradiënten voor een specifieke effectieve batch.
Als grads niet worden gemagnetiseerd (of de schaalfactor veranderd) voordat de accumulatie klaar is, zal de volgende achterwaartse iteratie de gemagnetiseerde grads aan de niet-gemagnetiseerde grads (of grads gemagnetiseerd door een andere factor) toevoegen, waarna het onmogelijk wordt om de geaccumuleerde niet-gemagnetiseerde grads te herstellen. Het moet worden toegepast op de stap.
- U kunt graden ongemagnetiseerd maken door ongemagnetiseerd te gebruiken net voor elke stap, nadat alle gemagnetiseerde graden voor de komende stap zijn geaccumuleerd.
Om een compleet en effectief batch te verzekeren, roep dan maar update aan aan het einde van elke iteratie aan waar u eerder step heeft genoemd. - enumerate(data) functie laat ons de batchindex behouden terwijl we door de data iteratie.
- Breng de lopende verliezen uit door iters_to_accumulate(loss / iters_to_accumulate). Dit reduceert de bijdrage van elke mini-batch die we verwerken door de verliesnorm te normaliseren. Als u de verliezen binnen elke batch gemiddeld, is de deling al correct en is geen verder normaliseren nodig. Dit stap kan niet nodig zijn afhankelijk van hoe u de verliezen berekent.
- Als we
scaler.scale(verlies).backward()
gebruiken in PyTorch, accumuleert PyTorch de gescaleerde gradiënten en slaat ze op tot weoptimizer.zero_grad()
aanroepen.
Gradiëntboete
Bij het implementeren van een gradiëntboete, wordt torch.autograd.grad() gebruikt om gradiënten op te bouwen, die samen de boetewaarde vormen, die dan aan de verlies wordt toegevoegd. Een L2-boete zonder schalen of autocasting wordt getoond in het voorbeeld hieronder.
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
output = model(input)
loss = loss_fn(output, target)
# Maakt gradiënten
grad_prams = torch.autograd.grad(outputs=loss,
inputs=model.parameters(),
create_graph=True)
# Berekent de boeteterm en voegt deze toe aan de verlies
grad_norm = 0
for grad in grad_prams:
grad_norm += grad.pow(2).sum()
grad_norm = grad_norm.sqrt()
loss = loss + grad_norm
loss.backward()
# U kunt gradiënten hier afsluiten
optimizer.step()
Tensors die aan torch.autograd.grad() worden gegeven moeten worden gescaleerd om een gradiëntboete te implementeren. Het is nodig de gradiënten uit te schalen voordat ze worden samengevoegd om de boetewaarde te verkrijgen. Aangezien de rekening van de boeteterm deel uitmaakt van de voorwaartse passage, moet dit plaatsvinden binnen een autocast-context.
Voor dezelfde L2-boete ziet het er hier uit:
scaler = GradScaler()
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
with autocast():
output = model(input)
loss = loss_fn(output, target)
# Voer verliesscaling uit voor de achterwaartse passage van autograd.grad, resulterend #scaled_grad_prams
scaled_grad_prams = torch.autograd.grad(outputs=scaler.scale(loss),
inputs=model.parameters(),
create_graph=True)
# Maak grad_prams aan voordat de boete wordt berekend (grad_prams moet #ongescaald zijn).
# Aangezien geen optimer eigendom heeft van scaled_grad_prams, wordt conventioneel delen gebruikt in plaats van scaler.unscale_:
inv_scaled = 1./scaler.get_scale()
grad_prams = [p * inv_scaled for p in scaled_grad_prams]
# De boeteterm wordt berekend en toegevoegd aan de verlies.
with autocast():
grad_norm = 0
for grad in grad_prams:
grad_norm += grad.pow(2).sum()
grad_norm = grad_norm.sqrt()
loss = loss + grad_norm
# Toepast schaling op de achterwaartse oproep.
# Accumuleert goed gescaande bladgradienten.
scaler.scale(loss).backward()
# U kunt hier unscale_ uitvoeren
# step() en update() gaan door als gewoonlijk.
scaler.step(optimizer)
scaler.update()
Werken met meerdere modellen, verliezen en optiemers
Scaler.scale moet op elke verlies aan uw netwerk aangeroepen worden als u er veel heeft.
Als u veel optimalisatoren in uw netwerk heeft, kunt u scaler.unscale op elk ervan uitvoeren, en moet u scaler.step op elk ervan aanroepen. Echter, scaler.update moet maar eenmaal worden gebruikt, nadat alle optimalisatoren zijn gestapeld die in deze iteratie zijn gebruikt:
scaler = torch.cuda.amp.GradScaler()
for epoch in epochs:
for input, target in data:
optimizer1.zero_grad()
optimizer2.zero_grad()
with autocast():
output1 = model1(input)
output2 = model2(input)
loss1 = loss_fn(2 * output1 + 3 * output2, target)
loss2 = loss_fn(3 * output1 - 5 * output2, target)
#Alhoewel het behouden van het grafiek niet direct aan AMP is gelinkt, is het aanwezig in dit #voorbeeld omdat beide backward() aanroepen delen van hetzelfde grafiekgedeelte hebben.
scaler.scale(loss1).backward(retain_graph=True)
scaler.scale(loss2).backward()
# Als u de gradiënten voor de parameters wilt bekijken of aanpassen die ze #bezitten, kunt u specificeren welke optimalisatoren expliciet worden ongedeeld..
scaler.unscale_(optimizer1)
scaler.step(optimizer1)
scaler.step(optimizer2)
scaler.update()
Elke optimalisator controleert zijn gradiënten voor infs/NaNs en maakt een individuele beslissing of hij de stap overspringt of niet. Sommige optimalisatoren zullen de stap overslaan terwijl andere dat niet doen. Step-overslaan gebeurt slechts eenmaal per enkele honderden iteraties; dus zal dat de convergentie niet beïnvloeden. Voor modellen met meerdere optimalisatoren kunt u het probleem rapporteren als u er na het toevoegen van gradient schaling slecht convergentie ziet.
Werken met meerdere GPUs
Een van de belangrijkste problemen met diepe leermodelen is dat ze steeds groter worden en niet meer op een enkele GPU kunnen worden getraind. Het kan lang duren om een model op een enkele GPU te trainen, en multi-GPU-trainen is nodig om de modellen zo snel mogelijk klaar te krijgen. Een bekende wetenschapper was in staat de ImageNet-trainingsperiode van twee weken tot 18 minuten te verkorten of de uitgebreidste en meest geavanceerde Transformer-XL binnen twee weken te trainen in plaats van vier jaar.
DataParallel en DistributedDataParallel
Zonder de kwaliteit te compromitteert, biedt PyTorch de beste combinatie van gemak van gebruik en controle. nn.DataParallel en nn.parallel.DistributedDataParallel zijn twee functies van PyTorch voor het verspreiden van training over meerdere GPU’s. U kunt deze gemakkelijke wrappers gebruiken en aanpassingen aanbrengen om de netwerken op meerdere GPU’s te trainen.
DataParallel binnen een enkele proces
Binnen een enkele machine helpt DataParallel het trainen uit te voeren over vele GPU’s.
Laten we eens dichterbij kijken hoe DataParallel eigenlijk in de praktijk werkt.
Bij het gebruik van DataParallel om een neuronetwerk te trainen, gebeuren de volgende stappen:
- Het minibatch wordt verdeeld op GPU:0.
- Split en verspreid min-batch naar alle beschikbare GPU’s.
- Kopieer het model naar de GPU’s.
- Het vooruitgangsproces gebeurt op alle GPU’s.
- Bereken de verliezen in relatie tot de netwerkuitvoer op GPU:0, alsook terugsturen van de verliezen naar de verschillende GPU’s. Gradienten moeten worden berekend op elke GPU.
- Ketels op GPU:0 sommeren en de optimeringsmethode toepassen om het model bij te werken.
Hieronder wordt gesproken over autocast, de behandeling die hier wordt besproken is enkel van toepassing op autocast. Het gedrag van GradScaler blijft onveranderd. Het is onbelangrijk of torch.nn.DataParallel voor elk apparaat threads aanmaakt om de voorwaartse doorlading uit te voeren. De autocast-status wordt in elke thread gecommuniceerd en het volgende zal werken:
model = Model_m()
p_model = nn.DataParallel(model)
# Zet autocast in het hoofdthread
with autocast():
# Er zal autocasting plaatsvinden in p_model.
output = p_model(input)
# loss_fn wordt ook autocast
loss = loss_fn(output)
Gedeelde gegevens parallel, een GPU per proces
De documentatie voor torch.nn.parallel.DistributedDataParallel raadt aan om per proces een GPU te gebruiken voor de beste prestaties. In dit geval lanceert DistributedDataParallel binnenkort geen threads intern; dus het gebruik van autocast en GradScaler wordt niet beïnvloed.
DistributedDataParallel, meerdere GPU’s per proces
Hier kan torch.nn.parallel.DistributedDataParallel een side thread aanmaken om de forward pass op elk apparaat te draaien, net als torch.nn.DataParallel. De oplossing is hetzelfde: schakel autocast in als onderdeel van uw model’s forward methode om ervoor te zorgen dat hij ingeschakeld is in side threads.
Conclusie
In dit artikel hebben we :
- Apex geïntroduceerd.
- Gezien hoe Amps werkt.
- Gezien hoe gradienten schalen, gradienten bijsnijden, gradienten accumuleren en gradienten boete.
- Gezien hoe we kunnen werken met meerdere modellen, verliezen en optimalisatoren.
- Gezien hoe we DataParallel kunnen uitvoeren in één proces bij het werken met meerdere GPU’s.
Referenties
https://developer.nvidia.com/blog/apex-pytorch-easy-mixed-precision-training/
https://nvidia.github.io/apex/amp.html
https://discuss.pytorch.org/t/accumulating-gradients/30020
https://towardsdatascience.com/how-to-scale-training-on-multiple-gpus-dae1041f49d2
Source:
https://www.digitalocean.com/community/tutorials/automatic-mixed-precision-using-pytorch