Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.
Modificare uno script PyTorch di addestramento
In questa sezione, imparerai come modificare gli script di PyTorch addestramento per configurare la libreria di parallelismo dei SageMaker modelli per il partizionamento automatico e il partizionamento manuale.
Nota
Per scoprire quali PyTorch versioni sono supportate dalla libreria, consulta. Framework e Regioni AWS supportati
Suggerimento
Per alcuni esempi di end-to-end quaderni che dimostrano come utilizzare uno script di PyTorch addestramento con la libreria di parallelismo dei SageMaker modelli, vedete. Esempi di Amazon SageMaker Model Parallelism Library v1
Si prega di notare che il partizionamento automatico è abilitato come impostazione predefinita. Se non diversamente specificato, gli script seguenti utilizzano il partizionamento automatico.
Argomenti
Divisione automatica con PyTorch
Le seguenti modifiche allo script di addestramento sono necessarie per eseguire uno script di PyTorch addestramento con la libreria SageMaker di parallelismo dei modelli:
-
Importa e inizializza la libreria con
smdistributed.modelparallel.torch.init()
. -
Esegui il wrapping del modello con
smdistributed.modelparallel.torch.DistributedModel
. Tieni presente che tutti i tensori restituiti dal metodo forward
dell'oggettonn.Module
sottostante verranno trasmessi su dispositivi paralleli al modello, con un sovraccarico di comunicazione, quindi non dovrebbe essere restituito alcun tensore che non è necessario al di fuori del metodo di chiamata (come le attivazioni intermedie).Nota
Per l'addestramento FP16, è necessario utilizzare il gestore di contesto smdistributed.modelparallel.torch.model_creation()
per eseguire il wrapping del modello. Per ulteriori informazioni, consulta FP16Formazione con Model Parallelism. -
Esegui il wrapping dell'ottimizzatore con
smdistributed.modelparallel.torch.DistributedOptimizer
. Nota
Per l'addestramento FP16, è necessario impostare il dimensionamento delle perdite statico o dinamico. Per ulteriori informazioni, consulta FP16Formazione con Model Parallelism.
-
Utilizza l'oggetto
DistributedModel
restituito anziché un modello utente. -
Inserisci la logica forward e backward in una funzione a fasi e decorala con
smdistributed.modelparallel.torch.step
. -
Limita ciascun processo al proprio dispositivo tramite
torch.cuda.set_device(smp.local_rank())
. -
Sposta i tensori di input sulla GPU utilizzando l'API
.to()
prima della chiamatasmp.step
(vedi esempio sotto riportato). -
Sostituisci
torch.Tensor.backward
etorch.autograd.backward
conDistributedModel.backward
. -
Esegui la post-elaborazione sugli output tra i microbatch utilizzando metodi
StepOutput
come reduce_mean
. -
Se è presente una fase di valutazione, inserisci in modo analogo la logica forward all'interno di una funzione decorata
smp.step
e post-elabora gli output utilizzando l'APIStepOutput
. -
Imposta
drop_last=True
inDataLoader
. In alternativa, salta manualmente un batch nel ciclo di addestramento se la dimensione del batch non è suddivisibile per il numero di microbatch.
import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torchnet.dataset import SplitDataset from torchvision import datasets import smdistributed.modelparallel.torch as smp class GroupedNet(nn.Module): def __init__(self): super(GroupedNet, self).__init__() # define layers def forward(self, x): # define forward pass and return model outputs # smdistributed: Define smp.step. Return any tensors needed outside. @smp.step def train_step(model, data, target): output = model(data) loss = F.nll_loss(output, target, reduction="mean") model.backward(loss) return output, loss def train(model, device, train_loader, optimizer): model.train() for batch_idx, (data, target) in enumerate(train_loader): # smdistributed: Move input tensors to the GPU ID used by the current process, # based on the set_device call. data, target = data.to(device), target.to(device) optimizer.zero_grad() # Return value, loss_mb is a StepOutput object _, loss_mb = train_step(model, data, target) # smdistributed: Average the loss across microbatches. loss = loss_mb.reduce_mean() optimizer.step() # smdistributed: initialize the backend smp.init() # smdistributed: Set the device to the GPU ID used by the current process. # Input tensors should be transferred to this device. torch.cuda.set_device(smp.local_rank()) device = torch.device("cuda") # smdistributed: Download only on a single process per instance. # When this is not present, the file is corrupted by multiple processes trying # to download and extract at the same time dataset = datasets.MNIST("../data", train=True, download=False) # smdistributed: Shard the dataset based on data-parallel ranks if smp.dp_size() > 1: partitions_dict = {f"{i}": 1 / smp.dp_size() for i in range(smp.dp_size())} dataset = SplitDataset(dataset, partitions=partitions_dict) dataset.select(f"{smp.dp_rank()}") # smdistributed: Set drop_last=True to ensure that batch size is always divisible # by the number of microbatches train_loader = torch.utils.data.DataLoader(dataset, batch_size=64, drop_last=True) model = GroupedNet() optimizer = optim.Adadelta(model.parameters(), lr=4.0) # smdistributed: Use the DistributedModel container to provide the model # to be partitioned across different ranks. For the rest of the script, # the returned DistributedModel object should be used in place of # the model provided for DistributedModel class instantiation. model = smp.DistributedModel(model) optimizer = smp.DistributedOptimizer(optimizer) train(model, device, train_loader, optimizer)
Divisione manuale con PyTorch
Usa i gestori di contesto smp.partition
smp.partition
viene inserito in default_partition
. Deve essere indicata default_partition
se auto_partition
è impostata su False
. I moduli creati in un contesto smp.partition
specifico vengono posizionati nella partizione corrispondente.
import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torchnet.dataset import SplitDataset from torchvision import datasets import smdistributed.modelparallel.torch as smp class GroupedNet(nn.Module): def __init__(self): super(GroupedNet, self).__init__() with smp.partition(0): # define child modules on device 0 with smp.partition(1): # define child modules on device 1 def forward(self, x): # define forward pass and return model outputs # smdistributed: Define smp.step. Return any tensors needed outside. @smp.step def train_step(model, data, target): output = model(data) loss = F.nll_loss(output, target, reduction="mean") model.backward(loss) return output, loss def train(model, device, train_loader, optimizer): model.train() for batch_idx, (data, target) in enumerate(train_loader): # smdistributed: Move input tensors to the GPU ID used by the current process, # based on the set_device call. data, target = data.to(device), target.to(device) optimizer.zero_grad() # Return value, loss_mb is a StepOutput object _, loss_mb = train_step(model, data, target) # smdistributed: Average the loss across microbatches. loss = loss_mb.reduce_mean() optimizer.step() # smdistributed: initialize the backend smp.init() # smdistributed: Set the device to the GPU ID used by the current process. # Input tensors should be transferred to this device. torch.cuda.set_device(smp.local_rank()) device = torch.device("cuda") # smdistributed: Download only on a single process per instance. # When this is not present, the file is corrupted by multiple processes trying # to download and extract at the same time dataset = datasets.MNIST("../data", train=True, download=False) # smdistributed: Shard the dataset based on data-parallel ranks if smp.dp_size() > 1: partitions_dict = {f"{i}": 1 / smp.dp_size() for i in range(smp.dp_size())} dataset = SplitDataset(dataset, partitions=partitions_dict) dataset.select(f"{smp.dp_rank()}") # smdistributed: Set drop_last=True to ensure that batch size is always divisible # by the number of microbatches train_loader = torch.utils.data.DataLoader(dataset, batch_size=64, drop_last=True) model = GroupedNet() optimizer = optim.Adadelta(model.parameters(), lr=4.0) # smdistributed: Use the DistributedModel container to provide the model # to be partitioned across different ranks. For the rest of the script, # the returned DistributedModel object should be used in place of # the model provided for DistributedModel class instantiation. model = smp.DistributedModel(model) optimizer = smp.DistributedOptimizer(optimizer) train(model, device, train_loader, optimizer)
Considerazioni
Quando configurate uno script di PyTorch addestramento utilizzando SageMaker la libreria di parallelismo dei modelli, dovete tenere presente quanto segue:
-
Se utilizzi una tecnica di ottimizzazione che si basa su norme riguardanti i gradienti globali, ad esempio una norma sui gradienti di tutto il modello, come alcune varianti dell'ottimizzatore LAMB o il ritaglio del gradiente globale, è necessario raccogliere tutte le norme delle partizioni del modello per verificarne la correttezza. A tale scopo è possibile utilizzare i tipi di dati di base per la comunicazione della libreria.
-
Tutti gli argomenti
torch.Tensor
relativi ai metodi forward dinn.Modules
nel modello devono essere utilizzati nel calcolo dell'output del modulo. In altre parole, la libreria non supporta il caso in cui esista un argomentotorch.Tensor
per un modulo da cui l'output del modulo non dipende. -
L'argomento della chiamata
smp.DistributedModel.backward()
deve dipendere da tutti gli output del modello. In altre parole, non può esserci un output della chiamatasmp.DistributedModel.forward
che non venga utilizzato nel calcolo del tensore utilizzato per la chiamatasmp.DistributedModel.backward
. -
Se nel codice sono presenti chiamate
torch.cuda.synchronize()
, potrebbe essere necessario chiamaretorch.cuda.set_device(smp.local_rank())
immediatamente prima della chiamata di sincronizzazione. Altrimenti potrebbero essere creati contesti CUDA non necessari nel dispositivo 0, che consumerebbe inutilmente memoria. -
Poiché la libreria colloca
nn.Modules
su dispositivi diversi, i moduli nel modello non devono dipendere da alcuno stato globale modificato all'internosmp.step
. È consentito qualsiasi stato che rimanga fisso durante l'addestramento o che venga modificato all'esterno dismp.step
in modo visibile a tutti i processi. -
Non è necessario spostare il modello nella GPU (ad esempio, utilizzando
model.to(device)
) quando si utilizza la libreria. Se provi a spostare il modello nella GPU prima che il modello sia partizionato (prima della prima chiamatasmp.step
), la chiamata di spostamento viene ignorata. La libreria sposta automaticamente la parte del modello assegnato a una classificazione nella sua GPU. Una volta iniziato l'addestramento con la libreria, non spostare il modello nella CPU e usalo, poiché non avrà i parametri corretti per i moduli non assegnati alla partizione tenuta dal processo. Se desideri riqualificare un modello o utilizzarlo per l'inferenza senza la libreria dopo che è stato addestrato utilizzando la libreria di parallelismo dei modelli, il modo consigliato è salvare il modello completo utilizzando la nostra API di checkpointing e caricarlo di nuovo su un normale Module. PyTorch -
Se disponi di un elenco di moduli tale per cui l'output di un modulo ne alimenta un altro, la sostituzione di tale elenco con
nn.Sequential
può migliorare significativamente le prestazioni. -
L'aggiornamento del peso (
optimizer.step()
) deve avvenire all'esterno dismp.step
perché è allora che l'intero passaggio indietro è terminato e i gradienti sono pronti. Quando si utilizza un modello ibrido con parallelismo di modelli e dati, a questo punto è garantito anche il completamento dei gradienti AllReduce . -
Quando usi la libreria in combinazione con il parallelismo dei dati, assicurati che il numero di batch su tutti i ranghi paralleli dei dati sia lo stesso in modo da AllReduce non bloccare l'attesa di un rank che non partecipa al passaggio.
-
Se avvii un processo di addestramento utilizzando un tipo di istanza ml.p4d (ad esempio ml.p4d.24xlarge), è necessario impostare la variabile del caricatore di dati
num_workers=0
. Ad esempio, puoi definire il tuoDataLoader
come di seguito indicato:dataloader = torch.utils.data.DataLoader( data, batch_size=batch_size, num_workers=0, pin_memory=True, drop_last=True, shuffle=shuffle, )
-
Gli input per
smp.step
devono essere gli input del modello generati daDataLoader
. Ciò è necessario perchésmp.step
suddivide internamente i tensori di input lungo la dimensione del batch e li inserisce in pipeline. Di conseguenza, passareDataLoader
alla funzionesmp.step
per generare gli input del modello all'interno non funziona.Ad esempio, se definisci un
DataLoader
come di seguito indicato:train_loader = torch.utils.data.DataLoader(dataset, batch_size=64, drop_last=True)
Dovresti accedere agli input del modello generati da
train_loader
e passarli a una funzione decoratasmp.step
. Non passaretrain_loader
direttamente alla funzionesmp.step
.def train(model, device, train_loader, optimizer): model.train() for batch_idx, (data, target) in enumerate(train_loader): ... _, loss_mb = train_step(model, data, target) ... @smp.step def train_step(model, data, target): ... return output, loss
-
I tensori di input per
smp.step
devono essere spostati sul dispositivo attuale utilizzando l'API.to()
, che deve avvenire dopo la chiamatatorch.cuda.set_device(local_rank())
.Ad esempio, puoi definire la funzione
train
nel modo seguente. Questa funzione aggiungedata
etarget
al dispositivo attuale utilizzando l'API.to()
prima di utilizzare i tensori di input per chiamaretrain_step
.def train(model, device, train_loader, optimizer): model.train() for batch_idx, (data, target) in enumerate(train_loader): # smdistributed: Move input tensors to the GPU ID used by the current process, # based on the set_device call. data, target = data.to(device), target.to(device) optimizer.zero_grad() # Return value, loss_mb is a StepOutput object _, loss_mb = train_step(model, data, target) # smdistributed: Average the loss across microbatches. loss = loss_mb.reduce_mean() optimizer.step()
I tensori di input per questa funzione decorata
smp.set
sono stati spostati nel dispositivo attuale nella funzionetrain
sopra riportata. Non è necessario spostare il modello nel dispositivo corrente. La libreria sposta automaticamente la parte del modello assegnato a una classificazione nella sua GPU.@smp.step def train_step(model, data, target): output = model(data) loss = F.nll_loss(output, target, reduction="mean") model.backward(loss) return output, loss
Funzionalità del framework non supportate
Le seguenti PyTorch funzionalità non sono supportate dalla SageMaker libreria di parallelismo dei modelli:
-
Se si utilizza il parallelismo dei dati con il PyTorch DDP
nativo, il modulo torch.nn.parallel.DistributedDataParallel
wrapper non è supportato dalla libreria. La libreria gestisce internamente l'integrazione con PyTorch DDP, inclusi i parametri broadcast e gradient. AllReduce Quando si utilizza la libreria, i buffer dei moduli vengono trasmessi una sola volta all'inizio dell'addestramento. Se il proprio modello ha buffer di moduli che devono essere sincronizzati tra gruppi paralleli di dati in ogni fase, è possibile farlo tramite l'API torch.distributed
, utilizzando il gruppo di processi che può essere ottenuto tramitesmp.get_dp_process_group()
. -
Per l'addestramento di precisione misto, il modulo
apex.amp
non è supportato. Per utilizzare la libreria con precisione mista automatica si consiglia di utilizzaretorch.cuda.amp
, utilizzando tuttaviasmp.amp.GradScaler
anziché l'implementazione in torch. -
torch.jit.ScriptModules
eScriptFunctions
non sono supportati dasmp.DistributedModel
. -
apex
:FusedLayerNorm
,FusedAdam
,FusedLAMB
eFusedNovoGrad
daapex
sono supportati. Puoi invece utilizzare le relative implementazioni della libreria tramite le APIsmp.optimizers
esmp.nn
.