Ändern Sie ein PyTorch Trainingsskript - Amazon SageMaker KI

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Ändern Sie ein PyTorch Trainingsskript

In diesem Abschnitt erfahren Sie, wie Sie PyTorch Trainingsskripte ändern, um die SageMaker Modellparallelitätsbibliothek für automatische Partitionierung und manuelle Partitionierung zu konfigurieren.

Anmerkung

Informationen darüber, welche PyTorch Versionen von der Bibliothek unterstützt werden, finden Sie unter. Unterstützte Frameworks und AWS-Regionen

Tipp

end-to-endNotebook-Beispiele, die veranschaulichen, wie ein PyTorch Trainingsskript mit der SageMaker Modellparallelitätsbibliothek verwendet wird, finden Sie unter. Beispiele für die Amazon SageMaker AI-Modellparallelismusbibliothek v1

Beachten Sie, dass die automatische Partitionierung standardmäßig aktiviert ist. Sofern nicht anders angegeben, verwenden die folgenden Skripten automatische Partitionierung.

Automatisiertes Teilen mit PyTorch

Die folgenden Änderungen am Trainingsskript sind erforderlich, um ein PyTorch Trainingsskript mit SageMaker der Modellparallelismus-Bibliothek auszuführen:

  1. Importieren und initialisieren Sie die Bibliothek mit smdistributed.modelparallel.torch.init().

  2. Schließen Sie das Modell mit smdistributed.modelparallel.torch.DistributedModel um. Beachten Sie, dass alle Tensoren, die von der forward Methode des zugrunde liegenden nn.Module Objekts zurückgegeben werden, über modellparallele Geräte übertragen werden, was zu Kommunikationsaufwand führt. Daher sollten alle Tensoren, die außerhalb der Aufrufmethode nicht benötigt werden (z. B. Zwischenaktivierungen), nicht zurückgegeben werden.

    Anmerkung

    Für das FP16 Training müssen Sie den Kontextmanager smdistributed.modelparallel.torch.model_creation () verwenden, um das Modell zu umschließen. Weitere Informationen finden Sie unter FP16Training mit Modellparallelität.

  3. Umschließen Sie den Optimierer mit smdistributed.modelparallel.torch.DistributedOptimizer.

    Anmerkung

    Für das Training müssen Sie eine statische oder dynamische Verlustskalierung einrichtenFP16. Weitere Informationen finden Sie unter FP16Training mit Modellparallelität.

  4. Verwenden Sie das zurückgegebene DistributedModel Objekt anstelle eines Benutzermodells.

  5. Fügen Sie die Vorwärts- und Rückwärtslogik in eine Schrittfunktion ein und dekorieren Sie sie mit smdistributed.modelparallel.torch.step.

  6. Beschränken Sie jeden Prozess auf sein eigenes Gerät durch torch.cuda.set_device(smp.local_rank()).

  7. Verschieben Sie die Eingangstensoren GPU unter Verwendung von vor dem Anruf auf den Wert .to() API vor dem smp.step Anruf (siehe Beispiel unten).

  8. Ersetzen Sie torch.Tensor.backward und torch.autograd.backward mit DistributedModel.backward.

  9. Führen Sie die Nachbearbeitung der Ausgaben für alle Mikrobatches mithilfe von StepOutput Methoden wie reduce_mean durch.

  10. Wenn es einen Bewertungsschritt gibt, platzieren Sie die Vorwärtslogik auf ähnliche Weise in einer mit smp.step -dekorierten Funktion und bearbeiten Sie die Ausgaben mithilfe von. StepOutputAPI

  11. drop_last=True in DataLoader einstellen. Alternativ können Sie einen Stapel in der Trainingsschleife manuell überspringen, wenn die Batchgröße nicht durch die Anzahl der Mikrobatches teilbar ist.

Weitere Informationen zur Modellparallelismus-Bibliothek SageMaker von finden Sie in der API Dokumentation. 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)

Manuelles Teilen mit PyTorch

Verwenden Sie smp.partition Kontextmanager, um Module auf bestimmten Geräten zu platzieren. Jedes Modul, das sich nicht in einem smp.partition Kontext befindet, wird in den default_partition platziert. Das default_partition muss angegeben werden, wenn auto_partition auf False gesetzt ist. Die Module, die in einem bestimmten smp.partition Kontext erstellt werden, werden auf der entsprechenden Partition platziert.

Weitere Informationen zur Modellparallelismus-Bibliothek SageMaker API von finden Sie in der Dokumentation. 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)

Überlegungen

Wenn Sie ein PyTorch Trainingsskript mithilfe SageMaker der Modellparallelitätsbibliothek konfigurieren, sollten Sie Folgendes beachten:

  • Wenn Sie eine Optimierungstechnik verwenden, die auf globalen Gradientennormen basiert, z. B. der Gradientennorm aus dem gesamten Modell, wie z. B. einige Varianten von LAMB Optimizer oder Global Gradient Clipping, müssen Sie alle Normen aus den Modellpartitionen zusammenstellen, um ihre Richtigkeit zu überprüfen. Zu diesem Zweck können Sie die grundlegenden Kommunikationsdatentypen der Bibliothek verwenden.

  • Alle torch.Tensor Argumente für die Vorwärtsmethoden von nn.Modules in Ihrem Modell müssen bei der Berechnung der Modulausgabe verwendet werden. Mit anderen Worten, die Bibliothek unterstützt nicht den Fall, dass es ein torch.Tensor Argument für ein Modul gibt, von dem die Modulausgabe nicht abhängt.

  • Das Argument für den smp.DistributedModel.backward() Aufruf muss von allen Modellausgaben abhängen. Mit anderen Worten, es darf keine Ausgabe des smp.DistributedModel.forward Aufrufs geben, die nicht bei der Berechnung des Tensors verwendet wird, der in den smp.DistributedModel.backward Aufruf eingespeist wird.

  • Wenn Ihr Code torch.cuda.synchronize() Aufrufe enthält, müssen Sie möglicherweise torch.cuda.set_device(smp.local_rank()) unmittelbar vor dem Synchronisierungsaufruf anrufen. Andernfalls könnten in Gerät 0 unnötige CUDA Kontexte erstellt werden, die unnötig Speicherplatz verbrauchen.

  • Da sich die Bibliothek nn.Modules auf unterschiedlichen Geräten befindet, dürfen die Module im Modell nicht von einem globalen Status abhängen, der im Inneren von smp.step geändert wird. Jeder Status, der während des gesamten Trainings unverändert bleibt oder außerhald von smp.step so verändert wird, dass er für alle Prozesse sichtbar ist, ist zulässig.

  • Sie müssen das Modell nicht verschieben GPU (z. B. verwendenmodel.to(device)), wenn Sie die Bibliothek verwenden. Wenn Sie versuchen, das Modell GPU vor der Partitionierung des Modells (vor dem ersten smp.step Aufruf) zu verschieben, wird der Move-Aufruf ignoriert. Die Bibliothek verschiebt den Teil des Modells, dem eine Rangfolge zugewiesen wurde, automatisch in ihrenGPU. Sobald das Training mit der Bibliothek begonnen hat, sollten Sie das Modell nicht mehr dorthin verschieben CPU und es verwenden, da es sonst keine korrekten Parameter für Module enthält, die nicht der vom Prozess gespeicherten Partition zugewiesen sind. Wenn Sie ein Modell neu trainieren oder es ohne die Bibliothek für Inferenz verwenden möchten, nachdem es mit der Modellparallelitätsbibliothek trainiert wurde, wird empfohlen, das vollständige Modell mithilfe unseres Checkpoints zu speichern API und es wieder in ein reguläres Modul zu laden. PyTorch

  • Wenn Sie eine Liste von Modulen haben, bei denen die Ausgabe eines Moduls in ein anderes einfließen kann, kann das Ersetzen dieser Liste durch die Leistung erheblich verbessern. nn.Sequential

  • Das Gewichtsupdate (optimizer.step()) muss außerhalb von smp.step erfolgen, da dann der gesamte Rückwärtsdurchlauf abgeschlossen ist und die Farbverläufe bereit sind. Wenn Sie ein Hybridmodell mit Modell- und Datenparallelität verwenden, ist zu diesem Zeitpunkt auch garantiert, dass die Gradienten beendet AllReduce sind.

  • Wenn Sie die Bibliothek in Kombination mit Datenparallelität verwenden, stellen Sie sicher, dass die Anzahl der Batches auf allen datenparallelen Rängen gleich ist, damit Sie AllReduce nicht auf einen Rang warten, der nicht am Schritt teilnimmt.

  • Wenn Sie einen Trainingsjob mit einem ml.p4d-Instance-Typ (z. B. ml.p4d.24xlarge) starten, müssen Sie die Dataloader-Variable num_workers=0 festlegen. Sie können DataLoader Ihren beispielsweise wie folgt definieren:

    dataloader = torch.utils.data.DataLoader( data, batch_size=batch_size, num_workers=0, pin_memory=True, drop_last=True, shuffle=shuffle, )
  • Die Eingaben für smp.step müssen die Modelleingaben sein, die von DataLoader generiert wurden. Der Grund dafür ist, dass smp.step die Eingabetensoren intern entlang der Stapeldimension aufteilt und sie in eine Pipeline einfügt. Dies bedeutet, dass es nicht funktioniert, DataLoader sich selbst an die smp.step Funktion zur Generierung der darin enthaltenen Modelleingaben zu übergeben.

    Wenn Sie beispielsweise a DataLoader wie folgt definieren:

    train_loader = torch.utils.data.DataLoader(dataset, batch_size=64, drop_last=True)

    Sie sollten auf die Modelleingaben zugreifen, die von generiert wurden, train_loader und diese an eine smp.step dekorierte Funktion übergeben. Übergeben Sie train_loader nicht direkt an die smp.step Funktion.

    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
  • Die zu verwendenden Eingangstensoren smp.step müssen auf das aktuelle Gerät verschoben werden .to()API, was nach dem Aufruf erfolgen muss. torch.cuda.set_device(local_rank())

    Sie können z. B. wie folgt die Funktion train definieren. Diese Funktion fügt dem aktuellen Gerättarget, das diese Eingangstensoren verwendet, data und hinzu, .to() API bevor diese Eingangstensoren zum Aufrufen verwendet werden. 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()

    Die Eingangstensoren für diese smp.set dekorierte Funktion wurden in der obigen train Funktion auf das aktuelle Gerät verschoben. Das Modell muss nicht auf das aktuelle Gerät verschoben werden. Die Bibliothek verschiebt den Teil des Modells, der einem Rang zugewiesen wurde, automatisch an seinenGPU.

    @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

Nicht unterstützte Framework-Funktionen

Die folgenden PyTorch Funktionen werden von der SageMaker Modellparallelitätsbibliothek nicht unterstützt:

  • Wenn Sie Datenparallelität mit der systemeigenen Version verwenden PyTorch DDP, wird das torch.nn.parallel.DistributedDataParallelWrapper-Modul von der Bibliothek nicht unterstützt. Die Bibliothek verwaltet intern die Integration mit PyTorch DDP, einschließlich Parameterübertragung und Gradient. AllReduce Bei Verwendung der Bibliothek werden Modulpuffer zu Beginn des Trainings nur einmal übertragen. Wenn Ihr Modell über Modulpuffer verfügt, die bei jedem Schritt über datenparallele Gruppen hinweg synchronisiert werden müssen, können Sie dies über die tun torch.distributedAPI, indem Sie die Prozessgruppe verwenden, die über smp.get_dp_process_group() abgerufen werden kann.

  • Für gemischtes Präzisionstraining wird das apex.amp Modul nicht unterstützt. Es wird empfohlen, die Bibliothek mit automatischer Mixed-Precision torch.cuda.amp zu verwenden, mit der Ausnahme, dass smp.amp.GradScaler anstelle der Implementierung in Torch verwendet wird.

  • torch.jit.ScriptModules und ScriptFunctions werden von smp.DistributedModel nicht unterstützt.

  • apex : FusedLayerNorm, FusedAdam FusedLAMB, und FusedNovoGrad von apex werden nicht unterstützt. Sie können stattdessen deren Bibliotheksimplementierungen durch smp.optimizers und smp.nn APIs verwenden.