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 di addestramento PyTorch
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, vedere. Esempi della libreria di parallelismo dei modelli Amazon SageMaker AI 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 la FP16 formazione, è necessario utilizzare il gestore di contesto smdistributed.modelparallel.torch.model_creation () per avvolgere il modello.
Per ulteriori informazioni, consulta FP16Formazione con Model Parallelism. -
Esegui il wrapping dell'ottimizzatore con
smdistributed.modelparallel.torch.DistributedOptimizer
. Nota
Per FP16 la formazione, è necessario impostare una scala statica o dinamica delle perdite. 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 sull'GPUutilizzo
.to()
API prima dellasmp.step
chiamata (vedi esempio sotto). -
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, posiziona in modo analogo la logica forward all'interno di una funzione
smp.step
-decorata e post-elabora gli output utilizzando.StepOutput
API -
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 essere consapevoli di quanto segue:
-
Se utilizzate una tecnica di ottimizzazione che si basa su norme globali di gradiente, ad esempio la norma del gradiente dell'intero modello, come alcune varianti di LAMB optimizer o di global gradient clipping, dovete raccogliere tutte le norme sulle 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 non necessari nel dispositivo 0, che consumerà inutilmente memoriaCUDA. -
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 in GPU (ad esempio, utilizzandolo
model.to(device)
) quando si utilizza la libreria. Se provate a spostare il modello GPU prima del partizionamento del modello (prima della primasmp.step
chiamata), la chiamata move viene ignorata. La libreria sposta automaticamente la parte del modello assegnata a un rango al suo. GPU Una volta iniziato l'addestramento con la libreria, non spostate il modello CPU e non usatelo, poiché non avrà i parametri corretti per i moduli non assegnati alla partizione contenuta dal processo. Se desideri riaddestrare un modello o usarlo per l'inferenza senza la libreria dopo che è stato addestrato utilizzando la libreria di parallelismo dei modelli, il modo consigliato è salvare il modello completo usando il nostro checkpoint API 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 da
smp.step
devono essere spostati sul dispositivo corrente in uso.to()
API, operazione che deve avvenire dopo la chiamata.torch.cuda.set_device(local_rank())
Ad esempio, puoi definire la funzione
train
nel modo seguente. Questa funzione aggiungedata
etarget
al dispositivo corrente utilizzando.to()
API prima di utilizzare quei tensori di input da chiamare.train_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 assegnata a un rango al suo. 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 libreria SageMaker di parallelismo dei modelli:
-
Se utilizzi il parallelismo dei dati con quello nativo PyTorch DDP
, 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 tuo modello ha buffer di moduli che devono essere sincronizzati tra gruppi paralleli di dati in ogni fase, puoi farlo tramite torch.distributed
API, utilizzando il gruppo di processi che può essere ottenuto tramite.smp.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. È possibile utilizzare le relative implementazioni della libreria tramitesmp.optimizers
e invece.smp.nn
APIs