Modificare uno script di addestramento PyTorch - Amazon SageMaker AI

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.

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:

  1. Importa e inizializza la libreria con smdistributed.modelparallel.torch.init().

  2. Esegui il wrapping del modello con smdistributed.modelparallel.torch.DistributedModel. Tieni presente che tutti i tensori restituiti dal metodo forward dell'oggetto nn.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.

  3. 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.

  4. Utilizza l'oggetto DistributedModel restituito anziché un modello utente.

  5. Inserisci la logica forward e backward in una funzione a fasi e decorala con smdistributed.modelparallel.torch.step.

  6. Limita ciascun processo al proprio dispositivo tramite torch.cuda.set_device(smp.local_rank()).

  7. Sposta i tensori di input sull'GPUutilizzo .to() API prima della smp.step chiamata (vedi esempio sotto).

  8. Sostituisci torch.Tensor.backward e torch.autograd.backward con DistributedModel.backward.

  9. Esegui la post-elaborazione sugli output tra i microbatch utilizzando metodi StepOutput come reduce_mean.

  10. 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. StepOutputAPI

  11. Imposta drop_last=True in DataLoader. In alternativa, salta manualmente un batch nel ciclo di addestramento se la dimensione del batch non è suddivisibile per il numero di microbatch.

Per saperne di più sulla libreria SageMaker di parallelismo dei modelliAPI, consulta la documentazione. API

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 per posizionare i moduli in dispositivi specifici. Qualsiasi modulo non inserito in alcun contesto 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.

Per saperne di più sulla libreria SageMaker di parallelismo dei modelliAPI, consulta la documentazione. API

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 di nn.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 argomento torch.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 chiamata smp.DistributedModel.forward che non venga utilizzato nel calcolo del tensore utilizzato per la chiamata smp.DistributedModel.backward.

  • Se nel codice sono presenti chiamate torch.cuda.synchronize(), potrebbe essere necessario chiamare torch.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'interno smp.step. È consentito qualsiasi stato che rimanga fisso durante l'addestramento o che venga modificato all'esterno di smp.step in modo visibile a tutti i processi.

  • Non è necessario spostare il modello in GPU (ad esempio, utilizzandolomodel.to(device)) quando si utilizza la libreria. Se provate a spostare il modello GPU prima del partizionamento del modello (prima della prima smp.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 di smp.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 tuo DataLoader 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 da DataLoader. Ciò è necessario perché smp.step suddivide internamente i tensori di input lungo la dimensione del batch e li inserisce in pipeline. Di conseguenza, passare DataLoader alla funzione smp.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 decorata smp.step. Non passare train_loader direttamente alla funzione smp.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 aggiunge data e target 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 funzione train 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.DistributedDataParallelwrapper 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.distributedAPI, 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 utilizzare torch.cuda.amp, utilizzando tuttavia smp.amp.GradScaler anziché l'implementazione in torch.

  • torch.jit.ScriptModules e ScriptFunctions non sono supportati da smp.DistributedModel.

  • apex: FusedLayerNorm, FusedAdam, FusedLAMB e FusedNovoGrad da apex sono supportati. È possibile utilizzare le relative implementazioni della libreria tramite smp.optimizers e invece. smp.nn APIs