Introduzione
I modelli di apprendimento profondo più grandi richiedono maggiori risorse di calcolo e di memoria. L’addestramento veloce di reti neurali profonde è stato ottenuto attraverso lo sviluppo di nuove tecniche. Invece dell’FP32 (formato di numeri floating-point a piena precisione), è possibile utilizzare l’FP16 (formato di numeri floating-point a metà precisione), e i ricercatori hanno scoperto che usarli insieme è un’opzione migliore.
La precisione mista consente l’addestramento a metà precisione mentre mantiene gran parte dell’accuratezza della rete a precisione singola. Il termine “tecnica di precisione mista” si riferisce al fatto che questo metodo utilizza sia rappresentazioni a precisione singola che a metà precisione.
In questo riepilogo dell’addestramento a precisione mista automatica (Amp) con PyTorch, mostriamo come funziona la tecnica, guidando passo a passo attraverso il processo di utilizzo di Amp, e discutiamo applicazioni più avanzate di tecniche Amp con schemi di codice per i utenti da integrare successivamente nel loro codice personale.
Prerequisiti
Conoscenza di base di PyTorch: Familiarità con PyTorch, inclusi i suoi concetti chiave come tensori, moduli e il ciclo di addestramento.
Comprensione dei fondamenti dell’apprendimento profondo: Concezioni come le reti neurali, la backpropagation e l’ottimizzazione.
Conoscenza dell’addestramento a precisione mista: La consapevolezza dei benefici e dei limiti dell’addestramento a precisione mista, incluso l’uso ridotto della memoria e la computazione più veloce.
Accesso ai Dispositivi Compatibili: Un GPU che supporta la precisione mista, come i GPU di NVIDIA con Core Tensor (ad esempio, architetture Volta, Turing, Ampere).
Configurazione di Python e CUDA: Un ambiente Python funzionante con PyTorch installato e CUDA configurato per l’accelerazione GPU.
Panoramica della Precisione Mista
Come la maggior parte degli strumenti di apprendimento profondo, PyTorch normalmente viene addestrato su dati a floating point di 32 bit (FP32). FP32, da un lato, non è sempre necessario per il successo. È possibile utilizzare un floating point di 16 bit per alcune operazioni, nelle quali FP32 richiede più tempo e memoria.
Pertanto, gli ingegneri di NVIDIA hanno sviluppato una tecnica che consente l’addestramento a precisione mista in FP32 per un numero limitato di operazioni mentre la maggior parte della rete viene eseguita in FP16.
- Convertire il modello per utilizzare il tipo float16 dove possibile.
- Mantenere i master weights float32 per accumulare aggiornamenti di peso ogni iterazione.
- L’utilizzo della perdita di scalatura per preservare piccole grandezze di gradienti.
Mixed-Precision in PyTorch
Per la ricerca mista di precisione, PyTorch offre già integrati un’abbondanza di funzionalità.
I parametri del modulo vengono convertiti in FP16 quando si chiama il metodo .half()
, e i dati del tensore vengono convertiti in FP16 quando si chiama .half()
. Verrà utilizzato il calcolo FP16 veloce per eseguire qualsiasi operazione su questi moduli o tensori. Le librerie matematiche di NVIDIA (cuBLAS e cuDNN) sono ben supportate da PyTorch. I dati dalla pipeline FP16 vengono processati utilizzando i Core Tensor per eseguire GEMM e convoluzioni. Per impiegare i Core Tensor in cuBLAS, le dimensioni di un GEMM ([M, K] x [K, N] -> [M, N]) devono essere multipli di 8.
Introduzione a Apex
Le utilità miste di precisione di Apex sono state create per aumentare la velocità di addestramento mantenendo l’accuratezza e la stabilità dell’addestramento a singola precisione. Apex può eseguire operazioni in FP16 o FP32, gestire automaticamente la conversione del parametro master e scalare automaticamente le perdite.
Apex è stato creato per semplificare l’inclusione della training con precisione mista nei modelli per i ricercatori. Amp, che sta per Automatic Mixed-Precision, è una delle caratteristiche di Apex, un’estensione leggera di PyTorch. Solo qualche riga aggiuntiva sul loro network è sufficiente perché gli utenti possano beneficiare della training con precisione mista tramite Amp. Apex è stato lanciato a CVPR 2018, e vale la pena di notare che la community di PyTorch ha mostrato un forte supporto ad Apex sin dalla sua pubblicazione.
Fatto solo un minimo cambiamento al modello in esecuzione, Amp rende in modo tale che non dovete preoccuparvi della precisione mista durante la creazione o l’esecuzione del vostro script. Le ipotesi di Amp potrebbero non adattarsi così bene nei modelli che utilizzano PyTorch in modi meno comuni, ma ci sono hook per aggiustare queste ipotesi come richiesto.
Amp offre tutti i vantaggi della training con precisione mista senza la necessità di gestire esplicitamente la scala di perdita o le conversioni di tipo. Il sito web di GitHub di Apex contiene le istruzioni per il processo di installazione, e la documentazione ufficiale API è disponibile qui.
Come funzionano gli Amps
Amp utilizza un paradigma whitelist/blacklist a livello logico. Le operazioni di tensore in PyTorch includono funzioni di rete neurale come torch.nn.functional.conv2d, funzioni matematiche semplici come torch.log, e metodi di tensore come torch.Tensor. add__. Esistono tre categorie principali di funzioni in questo universo:
- Whitelist: Funzioni che potrebbero beneficiare dalla velocità dell’aritmetica FP16. Applicazioni tipiche includono la moltiplicazione di matrici e la convoluzione.
- Blacklist: Gli input devono essere in FP32 per funzioni in cui può non essere sufficiente la precisione di 16 bit.
- Tutto il resto (ovunque funzioni rimangano): Funzioni che possono essere eseguite in FP16, ma il costo dell’conversione FP32 -> FP16 per eseguirle in FP16 non è giustificato poiché l’aumento di velocità non è significativo.
La task di Amp è semplice, almeno in teoria. Amp determina se una funzione di PyTorch è in whitelist, blacklist o nessuna delle due prima di chiamarla. Tutti gli argomenti dovrebbero essere convertiti a FP16 se nella whitelist o a FP32 se nella blacklist. Se nessuna delle due, solo assicurarsi che tutti gli argomenti siano dello stesso tipo. Questa politica non è così semplice da applicare in pratica come sembra.
Utilizzare Amp in Congiunzione con un Modello PyTorch
Per includere Amp in un script PyTorch esistente, segui questi passaggi:
- Usa la libreria Apex per importare Amp.
- Inizializza Amp in modo da poter apportare le modifiche necessarie al modello, all’ottimizzatore e alle funzioni interne di PyTorch.
- Note il punto in cui avviene la backpropagation (.backward()) in modo da permettere a Amp di scalare contemporaneamente la perdita e di eliminare lo stato per iterazione.
Step 1
C’è solo una riga di codice per il primo passaggio:
Step 2
Il modello di rete neurale e l’ottimizzatore utilizzati per l’addestramento devono essere già specificati per completare questo passaggio, che è lungo una sola riga.
Impostazioni aggiuntive consentono di personalizzare le modifiche alle tensioni e ai tipi di operazione di Amp. La funzione amp.initialize() accetta molti parametri, tra cui ne specificheremo solo tre:
- models (torch.nn.Module o lista di torch.nn.Modules) – Modelli da modificare/convertire.
- ottimizzatori (facoltativo, torch.optim.Optimizer o lista di torch.optim.Optimizers) – Ottimizzatori da modificare/castare. OBLIGATORIO per l’addestramento, facoltativo per l’inferenza.
- opt_level (str, facoltativo, default=“O1”) – Livello di ottimizzazione di precisione pura o mista. Valori accettati sono “O0”, “O1”, “O2” e “O3”, spiegati in dettaglio sopra. Ci sono quattro livelli di ottimizzazione:
O0 per l’addestramento in FP32: Non è una operazione. Non c’è da preoccuparsi perché il modello in entrata dovrebbe essere già in FP32, e O0 potrebbe aiutare a stabilire un punto di riferimento per l’accuratezza.
O1 per la precisione mista (raccomandato per l’uso tipico): Modifica tutte le funzioni di Tensor e Torch per usare uno schema di input cast whitelist-blacklist. In FP16, le operazioni whitelist, come ad esempio le operazioni di Gemm e le convoluzioni amichevoli con il Core Tensor, vengono eseguite. La softmax, ad esempio, è un’operazione blacklist che richiede precisione FP32. A meno che non sia specificatamente menzionato diversamente, O1 anche utilizza la scalatura dinamica della perdita.
O2 per la precisione mista “ quasi FP16”: O2 converte i pesi del modello in FP16, modifica il metodo forward del modello per convertire i dati di input in FP16, mantiene le batchnorms in FP32, mantiene i master weights in FP32, aggiorna le param_groups dell’ottimizzatore in modo che l’ottimizzatore.step() agisca direttamente sugli eventuali pesi in FP32 e implementa la scalatura dinamica della perdita (a meno di essere sovrascritto). A differenza di O1, O2 non modifica le funzioni di Torch o i metodi di Tensor.
O3 per l’addestramento con FP16: O3 potrebbe non essere così stabile come O1 e O2 in termini di precisione mista vera. Pertanto, potrebbe essere vantaggioso impostare una base di velocità per il tuo modello, contro la quale la efficienza di O1 e O2 possa essere valutata.
La proprietà aggiuntiva di sovrascrittura keep_batchnorm_fp32=True in O3 potrebbe aiutarti a determinare la “velocità della luce” se il tuo modello utilizza batch normalization, abilitando la batch normalization cudnn.
O0 e O3 non sono veri metodi di precisione mista, ma aiutano a impostare una base di accuratezza e velocità, rispettivamente. Una implementazione di precisione mista è definita come O1 e O2.
Potrai provare entrambi e vedere quale migliora di più la performance e l’accuratezza per il tuo modello specifico.
Step 3
Assicurati di identificare dove avviene il passo di backward nel tuo codice.
Appariranno alcune righe di codice simili a queste:
loss = criterion(…)
loss.backward()
optimizer.step()
Step4
Using the Amp context manager, you can enable loss scaling by simply wrapping the backward pass:
loss = criterion(…)
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
optimizer.step()
That’s all. You may now rerun your script with mixed-precision training turned on.
Capturing Function Calls
PyTorch manca di un oggetto modello statico o di un grafo nel quale agganciarsi e inserire le castings menzionati sopra, poiché è così flessibile e dinamico. Attraverso il “monkey patching” delle funzioni necessarie, Amp può intercettare e castare i parametri in modo dinamico.
Ad esempio, è possibile usare il codice seguente per assicurarsi che gli argomenti del metodo torch.nn.functional.linear siano sempre castati a fp16:
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
Anche se Amp può aggiungere miglioramenti per renderre il codice più resistente, chiamare Amp.init() effettivamente causa l’inserimento di monkey patches in tutte le funzioni di PyTorch relative, in modo che gli argomenti siano correttamente castati in runtime.
Minimizzazione Casts
Ogni peso viene castato FP32 -> FP16 una sola volta ogni iterazione poiché Amp mantiene un cache interno di tutti i cast dei parametri e li riutilizza come necessario. A ogni iterazione, il contesto gestore per il passo di backpropagation indica a Amp quando svuotare il cache.
Autocasting e Scaling Gradienti Utilizzando PyTorch
“Addestramento a precisione mista automatica” si riferisce alla combinazione di torch.cuda.amp.autocast e torch.cuda.amp.GradScaler. Utilizzando torch.cuda.amp.autocast, è possibile impostare l’autocasting solo per determinati settori. L’autocasting seleziona automaticamente la precisione per le operazioni GPU per ottimizzare l’efficienza mantenendo l’accuratezza.
Le istanze di torch.cuda.amp.GradScaler rendono più semplice l’esecuzione degli step di scaling dei gradienti. Il scaling dei gradienti riduce l’underflow dei gradienti, che aiuta le reti con gradienti float16 ad ottenere una migliore convergenza.
Ecco un pezzo di codice per dimostrare come usare autocast() per ottenere una precisione mista automatica in PyTorch:
# Crea modello e ottimizzatore in precisione predefinita
model = Net().cuda()
optimizer = optim.SGD(model.parameters(), ...)
# Crea un GradScaler una sola volta all'inizio dell'addestramento.
scaler = GradScaler()
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
# Esegue il passo in avanti con l'autocasting.
with autocast(device_type='cuda', dtype=torch.float16):
output = model(input)
loss = loss_fn(output, target)
# Le operazioni di backpropagation sono eseguite nella stessa data type scelta dall'autocast per le operazioni di forward corrispondenti.
scaler.scale(loss).backward()
# scaler.step() prima de-scala i gradienti dei parametri assegnati all'ottimizzatore.
scaler.step(optimizer)
# Aggiorna la scala per la prossima iterazione.
scaler.update()
Se la passata in avanti per una specifica operazione ha input float16, allora la passata in indietro per questa operazione produce gradienti float16, e float16 potrebbe non essere in grado di esprimere gradienti con magnitudini piccole.
L’aggiornamento dei parametri correlati sarà perso se questi valori vengono svuotati a zero (“scappamento verso il basso”).
La scalatura delle gradienti è una tecnica che utilizza un fattore di scala per moltiplicare le perdite del network e poi esegue una passata in indietro sulla perdita scalata per evitare lo scappamento verso il basso. È anche necessario scalare le gradienti in uscita attraverso il network con lo stesso fattore. Come conseguenza, i valori delle gradienti hanno una maggiore grandezza, che li impedisce di svuotarsi a zero.
Prima dell’aggiornamento dei parametri, ciascuna gradiente del parametro (.grad attributo) dovrebbe essere de-scaled in modo che il fattore di scala non interferisca con l’learning rate. Sia autocast che GradScaler possono essere usati indipendentemente poiché sono modulari.
Lavorare con Gradienti Non Scalati
Clipping delle Gradienti
Possiamo scalare tutti i gradienti utilizzando il metodo Scaler.scale(Loss).backward()
. Le proprietà .grad
dei parametri tra backward()
e scaler.step(optimizer)
devono essere descalate prima di modificarli o ispezionarli. Se si desidera limitare la norma globale (vedere torch.nn.utils.clip_grad_norm_()) o la maggiore magnitudine (vedere torch.nn.utils.clip_grad_value_()) del set di gradienti a un valore inferiore o uguale a una certa soglia (alcuni limiti imposti dall’utente), è possibile utilizzare una tecnica chiamata “clipping dei gradienti”.
Il clipping senza descalatura avrebbe come risultato che la norma/la maggiore magnitudine dei gradienti sia scalata, invalidando la soglia richiesta (che era prevista come soglia per i gradienti non scalati). I gradienti contenuti nei parametri forniti dall’ottimizzatore sono descalati da scaler.unscale (optimizer).
Potete descalare i gradienti di altri parametri che erano stati precedentemente forniti ad un altro ottimizzatore (come ad esempio optimizer1) utilizzando scaler.unscale (optimizer1). Illustriamo questo concetto aggiungendo due righe di codice:
# Descala i gradienti dei parametri assegnati all'ottimizzatore in loco
scaler.unscale_(optimizer)
# Poiché i gradienti dei parametri assegnati all'ottimizzatore sono descalati, viene applicato il clip come usuale:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)
Lavorare con scale gradienze
Accumulo di gradienti
L’accumulo di gradienti si basa su un concetto incredibilmente semplice. Invece di aggiornare i parametri del modello, si attende e accumula i gradienti attraverso successivi batch per calcolare la perdita e il gradiente.
Dopo un certo numero di batch, i parametri sono aggiornati in base al gradiente cumulato. Ecco un frammento di codice su come usare l’accumulo di gradienti usando pytorch:
scaler = GradScaler()
for epoch in epochs:
for i, (input, target) in enumerate(data):
with autocast():
output = model(input)
loss = loss_fn(output, target)
# normalizza la perdita
loss = loss / iters_to_accumulate
# Accumula gradienti scalati.
scaler.scale(loss).backward()
# aggiornamento pesi
if (i + 1) % iters_to_accumulate == 0:
# potrebbe scegliere di sfasare qui se desiderato
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad()
- L’accumulo di gradienti aggiunge i gradienti attraverso una dimensione di batch adeguata di batch_per_iter * iters_to_accumulate.
La scala deve essere calibrata per il batch effettivo; questo comporta il controllo di gradi inf/NaN, l’omissione dell’step se qualunque inf/NaN sono rilevati, e l’aggiornamento della scala alla granularità del batch effettivo.
E ‘anche cruciale mantenere i gradi in una scala scalata e coerente quando vengono aggiunti i gradi per un particolare batch effettivo.
Se i gradi non sono scalati (o il fattore di scala cambia) prima dell’completamento dell’accumulo, il passo indietro successivo aggiungerà gradi scalati a gradi non scalati (o gradi scalati da un fattore diverso), dopo di che è impossibile recuperare i gradi accumulati non scalati da applicare.
- Puoi de-scalare i gradi utilizzando unscale poco prima dello step, dopo che tutti i gradi scalati per l’step successivo sono stati accumulati.
Per assicurarti di avere un batch efficace completo, chiami solo update alla fine di ogni iterazione in cui hai chiamato prima step - enumerate(data) function ci permette di tenere traccia dell’indice del batch mentre iteriamo attraverso i dati.
- Dividere la perdita in corso per iters_to_accumulate(loss / iters_to_accumulate). Questo riduce la contribuzione di ciascuna mini-batch che stiamo processando normalizzando la perdita. Se fai la media della perdita all’interno di ogni batch, la divisione è già corretta e non serve ulteriore normalizzazione. Questo passo potrebbe non essere necessario a seconda di come calcoli la perdita.
- Quando usiamo
scaler.scale(loss).backward()
, PyTorch accumula le gradienti scalate e le conserva fino a quando non chiamiamooptimizer.zero_grad()
.
Penalità di gradiente
Quando implementi una penalità di gradiente, torch.autograd.grad() viene usato per costruire le gradienti, che vengono combinate per formare il valore della penalità, che poi viene aggiunto alla perdita. Una penalità L2 scalata o autocastata non è mostrata nell’esempio seguente.
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
output = model(input)
loss = loss_fn(output, target)
# Crea gradienti
grad_prams = torch.autograd.grad(outputs=loss,
inputs=model.parameters(),
create_graph=True)
# Calcola il termine di penalità e lo aggiunge alla perdita
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()
# Puoi tagliare le gradienti qui
optimizer.step()
I tensori forniti a torch.autograd.grad() dovrebbero essere scalati per implementare una penalità di gradiente. È necessario sfasare le gradienti prima di combinarli per ottenere il valore della penalità. Poiché il calcolo del termine di penalità è parte dell’passo in avanti, deve avvenire all’interno di un contesto autocast.
Per la stessa penalità L2, ecco come si presenta:
scaler = GradScaler()
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
with autocast():
output = model(input)
loss = loss_fn(output, target)
# Esegue la scala per la perdita per il passo di backpropagation di autograd.grad, # risultando #scaled_grad_prams
scaled_grad_prams = torch.autograd.grad(outputs=scaler.scale(loss),
inputs=model.parameters(),
create_graph=True)
# Crea grad_prams prima di calcolare la penalità (grad_prams deve essere #scala).
# Poiché nessuno ottimizzatore possiede scaled_grad_prams, la divisione # convenzionale viene utilizzata invece di scaler.unscale_:
inv_scaled = 1./scaler.get_scale()
grad_prams = [p * inv_scaled for p in scaled_grad_prams]
# La clausola di penale viene calcolata e aggiunta alla perdita.
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
# Applica la scala alla chiamata di back.
# Accumula correttamente le gradienti di foglie scalate.
scaler.scale(loss).backward()
# Puoi disfare la scala qui
# step() e update() proseguono come di solito.
scaler.step(optimizer)
scaler.update()
Lavorare con molti modelli, perdite e ottimizzatori
Scaler.scale deve essere chiamato su ciascuna perdita presente nella rete se ne ha molte.
Se nella rete ci sono molti ottimizzatori, è possibile eseguire scaler.unscale su qualsiasi di essi, e deve essere chiamato scaler.step su ciascuno di loro. Tuttavia, scaler.update dovrebbe essere usato una sola volta, dopo aver eseguito il passo di tutti gli ottimizzatori utilizzati in questa iterazione:
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)
#Nonostante il保留图 non sia legato a amp, è presente in questo #esempio poiché entrambi i call a backward() condividono certi settori di grafo.
scaler.scale(loss1).backward(retain_graph=True)
scaler.scale(loss2).backward()
# Se si desidera visualizzare o modificare i gradienti dei parametri che #possiedono, è possibile specificare quali ottimizzatori ricevono l'unscaling esplicito. .
scaler.unscale_(optimizer1)
scaler.step(optimizer1)
scaler.step(optimizer2)
scaler.update()
Ogni ottimizzatore esamina i suoi gradienti per inf/NaN e decide individualmente se saltare o meno il passo. Alcuni ottimizzatori potrebbero saltare il passo, mentre altri potrebbero non farlo. Il saltare il passo avviene una sola volta ogni qualche centinaio di iterazioni; quindi non dovrebbe influire sulla convergenza. Per i modelli con multipli ottimizzatori, è possibile segnalare il problema se noti una cattiva convergenza dopo aver aggiunto la scalatura del gradiente.
Lavorare con più GPU
Uno dei problemi più significativi riguardanti i modelli di Deep Learning è che stanno diventando troppo grandi da essere addestrati su una sola GPU. Può richiedere troppo tempo addestrare un modello su una sola GPU, pertanto è necessario l’addestramento multiplo GPU per rendere i modelli pronti il più velocemente possibile. Un ricercatore ben noto è stato in grado di abbreviare la fase di addestramento di ImageNet da due settimane a 18 minuti o addestrare il più esteso e avanzato Transformer-XL in due settimane invece di quattro anni.
DataParallel e DistributedDataParallel
Senza compromettere la qualità, PyTorch offre la migliore combinazione di facilità d’uso e controllo. nn.DataParallel e nn.parallel.DistributedDataParallel sono due funzionalità di PyTorch per distribuire l’addestramento su molti GPU. È possibile utilizzare questi wrapper facili da usare e modifiche per addestrare la rete su molti GPU.
DataParallel in un singolo processo
In un singolo computer, DataParallel aiuta a diffondere l’addestramento su molti GPU.
Scopriamo più da vicino come DataParallel funziona realmente in pratica.
Quando si utilizza DataParallel per addestrare una rete neurale, si verificano i seguenti step:
- Il mini-batch è diviso su GPU:0.
- Split e distribuzione del mini-batch a tutti i GPU disponibili.
- Copiare il modello nei GPU.
- Esecuzione del passo di input su tutti i GPU.
- Calcolare la perdita in relazione agli output della rete su GPU:0, nonché restituire le perdite ai vari GPU. I gradienti dovrebbero essere calcolati su ogni GPU.
- Sommare i gradienti sulla GPU:0 e applicare l’ottimizzatore per aggiornare il modello.
Occorre sottolineare che le preoccupazioni discusse qui si applicano soltanto a autocast. Il comportamento di GradScaler rimane invariato. Non importa se torch.nn.DataParallel crea thread per ogni dispositivo per condurre il passo in avanti. Lo stato di autocast è comunicato in ognuno, e il seguente funzionerà:
model = Model_m()
p_model = nn.DataParallel(model)
#Imposta l'autocast nel thread principale
with autocast():
# ci sarà l'autocast in p_model.
output = p_model(input)
# loss_fn anche autocast
loss = loss_fn(output)
DistributedDataParallel, una GPU per processo
La documentazione per torch.nn.parallel.DistributedDataParallel consiglia l’uso di un GPU per processo per ottenere il massimo delle prestazioni. In questa situazione, DistributedDataParallel non avvia thread internamente; quindi l’uso di autocast e GradScaler non è influenzato.
DistributedDataParallel, GPU multipli per processo
Qui torch.nn.parallel.DistributedDataParallel può generare un thread secondario per eseguire il passo in avanti su ciascuno degli strumenti, come torch.nn.DataParallel. La correzione è la stessa: applicare autocast come parte del metodo forward del tuo modello per assicurarti che sia abilitato nei thread secondari.
Conclusione
In questo articolo abbiamo :
- Introdotto Apex.
- Visto come funziona Amps.
- Visto come si può eseguire la scala delle gradienti, il taglio delle gradienti, l’accumulo delle gradienti e la penalità gradiente.
- Visto come possiamo lavorare con molti modelli, perdite e ottimizzatori.
- Visto come possiamo eseguire DataParallel in un singolo processo quando ci si occupa di molti GPU.
Riferimenti
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