Sie trainieren Ihr neuestes KI -Modell und beobachten gespannt, wie der Verlust plötzlich – Growth! Ihre Protokolle sind mit Nans (nicht einer Zahl) überflutet – Ihr Modell ist irreparabel beschädigt und Sie sind verzweifelt auf Ihren Bildschirm. Um die Sache noch schlimmer zu machen, erscheinen die Nans nicht konsequent. Manchmal fährt Ihr Modell in Ordnung; In anderen Fällen scheitert es unerklärlicherweise. Manchmal stürzt es sofort ab, manchmal nach vielen Tagen des Trainings.
Nans in Tiefes Lernen Workloads gehören zu den frustrierendsten Problemen, die zu begegnen sind. Und weil sie oft sporadisch erscheinen – ausgelöst durch eine spezifische Kombination aus Modellzustand, Eingabedaten und stochastischen Faktoren – können sie unglaublich schwer zu reproduzieren und zu debuggen sein.
Angesichts der beträchtlichen Kosten für Schulungs -KI -Modelle und der potenziellen Abfälle, die durch NAN -Fehler verursacht werden, wird empfohlen, dedizierte Werkzeuge zur Erfassung und Analyse von NAN -Vorkommen zu haben. In a Vorheriger BeitragWir diskutierten die Herausforderung, NANS in einer Workload für Tensorflow -Trainingsanlagen zu debuggen. Wir haben ein effizientes Schema zur Erfassung und Reproduktion von NANS vorgeschlagen und eine Tensorflow -Implementierung von Stichproben geteilt. In diesem Beitrag übernehmen und zeigen wir einen ähnlichen Mechanismus für die Debugie von NANS in Pytorch -Workloads. Das allgemeine Schema lautet wie folgt:
Bei jedem Trainingsschritt:
- Speichern Sie eine Kopie des Trainingseingangsstapels.
- Überprüfen Sie die Gradienten für NAN -Werte. Wenn Sie angezeigt werden, speichern Sie einen Kontrollpunkt mit den aktuellen Modellgewichten, bevor das Modell beschädigt ist. Speichern Sie außerdem die Eingabestapel und gegebenenfalls den stochastischen Zustand. Die Schulungsjob einstellen.
- Reproduzieren und debuggen das Nan -Auftreten durch das Laden des gespeicherten Experimentszustands.
Obwohl dieses Schema leicht in nativem Pytorch implementiert werden kann, werden wir die Gelegenheit nutzen, um einige der Annehmlichkeiten von zu demonstrieren Pytorch Lightning -Ein leistungsstarkes Open-Supply-Framework, das die Entwicklung von ML-Modellen (maschinelles Lernen) optimieren soll. Auf Pytorch basiert auf Lightning abstracts viele der Kesselplattenkomponenten eines ML-Experiments wie Trainingsschleifen, Datenverteilung, Protokollierung und mehr, sodass Entwickler sich auf die Kernlogik ihrer Modelle konzentrieren können.
Um unser NAN -Erfassungsschema umzusetzen, werden wir verwenden Rückruf von Blitz Schnittstelle – Eine dedizierte Struktur, die das Einfügen von benutzerdefinierter Logik an bestimmten Stellen während des Ausführungsflusss ermöglicht.
Wichtig ist, dass Sie bitte unsere Wahl des Blitzes oder eines anderen Werkzeugs oder einer anderen Technik, die wir als Bestätigung seiner Verwendung erwähnen, nicht betrachten. Der Code, den wir teilen werden, ist für demonstrative Zwecke bestimmt – bitte verlassen Sie sich nicht auf seine Richtigkeit oder Optimalität.
Vielen Dank an Rom Maltser für seine Beiträge zu diesem Beitrag.
Nancapture -Rückruf
Um unsere NAN -Erfassungslösung zu implementieren, erstellen wir einen Nancapture Lightning -Rückruf. Der Konstruktor erhält einen Verzeichnispfad zum Speichern/Laden von Kontrollpunkten und richtet den Nancapture -Zustand ein. Wir definieren auch Versorgungsunternehmen für die Überprüfung von NANS, den Speichern von Kontrollpunkten und zum Stillstand des Trainingsjobs.
import os
import torch
from copy import deepcopy
import lightning.pytorch as pl
class NaNCapture(pl.Callback):
def __init__(self, dirpath: str):
# path to checkpoint
self.dirpath = dirpath
# replace to True when Nan is recognized
self.nan_captured = False
# shops a duplicate of the final batch
self.last_batch = None
self.batch_idx = None
@staticmethod
def contains_nan(tensor):
return torch.isnan(tensor).any().merchandise()
# alternatively test for finite
# return not torch.isfinite(tensor).merchandise()
@staticmethod
def halt_training(coach):
coach.should_stop = True
# talk cease command to all different ranks
coach.technique.reduce_boolean_decision(coach.should_stop,
all=False)
def save_ckpt(self, coach):
os.makedirs(self.dirpath, exist_ok=True)
# embody coach.global_rank to keep away from battle
filename = f"nan_checkpoint_rank_{coach.global_rank}.ckpt"
full_path = os.path.be part of(self.dirpath, filename)
print(f"saving ckpt to {full_path}")
coach.save_checkpoint(full_path, False)
Rückruffunktion: on_train_batch_start
Wir beginnen mit der Implementierung der on_train_batch_start Haken Sie eine Kopie jeder Eingabestapel auf. Im Falle eines NAN -Ereignisses wird diese Stapel im Kontrollpunkt gespeichert.
Rückruffunktion: on_before_optimizer_step
Als nächstes implementieren wir die on_before_optimizer_step Haken. Hier suchen wir nach NAN -Einträgen in allen Tensoren der Gradienten. Wenn wir gefunden werden, speichern wir einen Kontrollpunkt mit den nicht festgelegten Modellgewichten und stoppen das Coaching.
Python"> def on_before_optimizer_step(self, coach, pl_module, optimizer):
if not self.nan_captured:
# Examine if gradients include NaN
grads = (p.grad.view(-1) for p in pl_module.parameters()
if p.grad shouldn't be None)
all_grads = torch.cat(grads)
if self.contains_nan(all_grads):
print("nan discovered")
self.save_ckpt(coach)
self.halt_training(coach)
Erfassen des Trainingszustands
Um die Reproduzierbarkeit zu ermöglichen, nehmen wir den Nancapture -Zustand in den Kontrollpunkt ein, indem wir ihn dem Schulungsstatuswörterbuch anschließen. Lightning bietet dedizierte Dienstprogramme zum Speichern und Laden von a Rückrufstatus:
def state_dict(self):
d = {"nan_captured": self.nan_captured}
if self.nan_captured:
d("last_batch") = self.last_batch
return d
def load_state_dict(self, state_dict):
self.nan_captured = state_dict.get("nan_captured", False)
if self.nan_captured:
self.last_batch = state_dict("last_batch")
Reproduktion des Nan -Auftretens
Wir haben beschrieben, wie unser Nancapture -Rückruf verwendet werden kann, um den Schulungszustand zu speichern, der zu einer Nan geführt hat, aber wie können wir diesen Zustand neu laden, um das Downside zu reproduzieren und es zu debuggen? Um dies zu erreichen, nutzen wir die dedizierte Datenladeklasse von Lightning. LightningDatamodule.
DataModule -Funktion: on_before_batch_transfer
Im folgenden Codeblock erweitern wir die LightningDatamodule Klasse, mit dem die Injektion eines festen Trainingseingangs -Stapels injiziert werden kann. Dies wird erreicht, indem das überschrieben wird on_before_batch_transfer Haken, wie unten gezeigt:
from lightning.pytorch import LightningDataModule
class InjectableDataModule(LightningDataModule):
def __init__(self):
tremendous().__init__()
self.cached_batch = None
def set_custom_batch(self, batch):
self.cached_batch = batch
def on_before_batch_transfer(self, batch, dataloader_idx):
if self.cached_batch:
return self.cached_batch
return batch
Rückruffunktion: on_train_start
Der letzte Schritt besteht darin, die zu ändern on_train_start Haken unseres Nancapture -Rückrufs, um die gespeicherte Trainingsstapel in die zu injizieren LightningDatamodule.
def on_train_start(self, coach, pl_module):
if self.nan_captured:
datamodule = coach.datamodule
datamodule.set_custom_batch(self.last_batch)
Im nächsten Abschnitt werden wir die Finish-to-Finish-Lösung anhand eines Spielzeugbeispiels demonstrieren.
Spielzeugbeispiel
Um unseren neuen Rückruf zu testen, erstellen wir eine resnet50-Basierendes Bildklassifizierungsmodell mit einer Verlustfunktion, die absichtlich entwickelt wurde, um NAN -Vorkommen auszulösen.
Anstatt den Normal zu verwenden Kreuzung Verlust, wir berechnen BINARY_CROSS_ENTROPY_WITH_LOGITS für jede Klasse unabhängig und trennen Sie das Ergebnis durch die Anzahl der zu dieser Klasse gehörenden Stichproben. Unweigerlich werden wir auf eine Cost begegnen, in der ein oder mehrere Klassen fehlen, was zu einer Divide-by-Null-Operation führt, was zu NAN-Werten führt und das Modell kündigt.
Die folgende Implementierung folgt Lightning’s Einführungs -Tutorial.
import lightning.pytorch as pl
import torch
import torchvision
import torch.nn.practical as F
num_classes = 20
# outline a lightning module
class ResnetModel(pl.LightningModule):
def __init__(self):
"""Initializes a brand new occasion of the MNISTModel class."""
tremendous().__init__()
self.mannequin = torchvision.fashions.resnet50(num_classes=num_classes)
def ahead(self, x):
return self.mannequin(x)
def training_step(self, batch, batch_nb):
x, y = batch
outputs = self(x)
# uncomment for default loss
# return F.cross_entropy(outputs, y)
# calculate binary_cross_entropy for every class individually
losses = ()
for c in vary(num_classes):
rely = torch.count_nonzero(y==c)
masked = torch.the place(y==c, 1., 0.)
loss = F.binary_cross_entropy_with_logits(
outputs(..., c),
masked,
discount='sum'
)
mean_loss = loss/rely # might lead to NaN
losses.append(mean_loss)
total_loss = torch.stack(losses).imply()
return total_loss
def configure_optimizers(self):
return torch.optim.Adam(self.parameters(), lr=0.02)
Wir definieren einen synthetischen Datensatz und verkapulieren ihn in unserem InjectableDataModule
Klasse:
import os
import random
from torch.utils.information import Dataset, DataLoader
batch_size = 128
num_steps = 800
# A dataset with random pictures and labels
class FakeDataset(Dataset):
def __len__(self):
return batch_size*num_steps
def __getitem__(self, index):
rand_image = torch.randn((3, 224, 224), dtype=torch.float32)
label = torch.tensor(random.randint(0, num_classes-1),
dtype=torch.int64)
return rand_image, label
# outline a lightning datamodule
class FakeDataModule(InjectableDataModule):
def train_dataloader(self):
dataset = FakeDataset()
return DataLoader(
dataset,
batch_size=batch_size,
num_workers=os.cpu_count(),
pin_memory=True
)
Schließlich initialisieren wir einen Blitz Coach Mit unserem Nancapture Callback und Name Coach.match mit unserem Blitzmodul und Blitzdatenmodul.
import time
if __name__ == "__main__":
# Initialize a lightning module
lit_module = ResnetModel()
# Initialize a DataModule
mnist_data = FakeDataModule()
# Practice the mannequin
ckpt_dir = "./ckpt_dir"
coach = pl.Coach(
max_epochs=1,
callbacks=(NaNCapture(ckpt_dir))
)
ckpt_path = None
# test is nan ckpt exists
if os.path.isdir(ckpt_dir):
# test if nan ckpt exists
if os.path.isdir(ckpt_dir):
dir_contents = (os.path.be part of(ckpt_dir, f)
for f in os.listdir(ckpt_dir))
ckpts = (f for f in dir_contents
if os.path.isfile(f) and f.endswith('.ckpt'))
if ckpts:
ckpt_path = ckpts(0)
t0 = time.perf_counter()
coach.match(lit_module, mnist_data, ckpt_path=ckpt_path)
print(f"whole runtime: {time.perf_counter() - t0}")
Nach einer Reihe von Trainingsschritten erfolgt ein NAN -Ereignis. Zu diesem Zeitpunkt wird ein Checkpoint mit dem vollständigen Trainingszustand gespeichert und das Coaching gestoppt.
Wenn das Skript erneut ausgeführt wird, wird der genaue Zustand, der dazu geführt hat, dass die NAN neu geladen wird, so, dass wir das Downside leicht reproduzieren und seine Grundursache debuggen.
Leistungsaufwand
Um die Auswirkungen unseres Nancapture -Rückrufs auf die Laufzeitleistung zu bewerten, haben wir unser Experiment zur Verwendung geändert Crosseltropyloss (Um NANs zu vermeiden) und maß den durchschnittlichen Durchsatz beim Laufen mit und ohne Nancapture -Rückruf. Die Experimente wurden an einem durchgeführt Nvidia L40S GPUmit a Pytorch 2.5.1 Docker Bild.

Für unser Spielzeugmodell fügt der Nancapture -Rückruf der Laufzeitleistung minimal 1,5% zu.
Natürlich hängt der tatsächliche Overhead von den Einzelheiten des Modells und der Laufzeitumgebung ab.
Wie man mit der Stochastizität umgeht
Die Lösung, die wir von nun an beschrieben haben, wird es schaffen, den Trainingszustand zu reproduzieren, vorausgesetzt, das Modell enthält keine Zufälligkeit. Die Einführung der Stochastizität in die Modelldefinition ist jedoch häufig für die Konvergenz von entscheidender Bedeutung. Ein häufiges Beispiel für eine stochastische Schicht ist fackel.nn.dropout.
Sie können feststellen, dass Ihr NAN -Ereignis von dem genauen Zustand der Zufälligkeit abhängt, wenn der Fehler aufgetreten ist. Infolgedessen möchten wir unseren Rückruf von Nancapture verbessern, um den Zufallszustand zum Ausfall zu erfassen und wiederherzustellen. Der Zufallszustand wird durch eine Reihe von Bibliotheken bestimmt. Im folgenden Codeblock versuchen wir, den vollständigen Zustand der Zufälligkeit zu erfassen:
import os
import torch
import random
import numpy as np
from copy import deepcopy
import lightning.pytorch as pl
class NaNCapture(pl.Callback):
def __init__(self, dirpath: str):
# path to checkpoint
self.dirpath = dirpath
# replace to True when Nan is recognized
self.nan_captured = False
# shops a duplicate of the final batch
self.last_batch = None
self.batch_idx = None
# rng state
self.rng_state = {
"torch": None,
"torch_cuda": None,
"numpy": None,
"random": None
}
@staticmethod
def contains_nan(tensor):
return torch.isnan(tensor).any().merchandise()
# alternatively test for finite
# return not torch.isfinite(tensor).merchandise()
@staticmethod
def halt_training(coach):
coach.should_stop = True
coach.technique.reduce_boolean_decision(coach.should_stop,
all=False)
def save_ckpt(self, coach):
os.makedirs(self.dirpath, exist_ok=True)
# embody coach.global_rank to keep away from battle
filename = f"nan_checkpoint_rank_{coach.global_rank}.ckpt"
full_path = os.path.be part of(self.dirpath, filename)
print(f"saving ckpt to {full_path}")
coach.save_checkpoint(full_path, False)
def on_train_start(self, coach, pl_module):
if self.nan_captured:
# inject batch
datamodule = coach.datamodule
datamodule.set_custom_batch(self.last_batch)
def on_train_batch_start(self, coach, pl_module, batch, batch_idx):
if self.nan_captured:
# restore random state
torch.random.set_rng_state(self.rng_state("torch"))
torch.cuda.set_rng_state_all(self.rng_state("torch_cuda"))
np.random.set_state(self.rng_state("numpy"))
random.setstate(self.rng_state("random"))
else:
# seize present batch
self.last_batch= deepcopy(batch)
self.batch_idx = batch_idx
# seize present random state
self.rng_state("torch") = torch.random.get_rng_state()
self.rng_state("torch_cuda") = torch.cuda.get_rng_state_all()
self.rng_state("numpy") = np.random.get_state()
self.rng_state("random") = random.getstate()
def on_before_optimizer_step(self, coach, pl_module, optimizer):
if not self.nan_captured:
# Examine if gradients include NaN
grads = (p.grad.view(-1) for p in pl_module.parameters()
if p.grad shouldn't be None)
all_grads = torch.cat(grads)
if self.contains_nan(all_grads):
print("nan discovered")
self.save_ckpt(coach)
self.halt_training(coach)
def state_dict(self):
d = {"nan_captured": self.nan_captured}
if self.nan_captured:
d("last_batch") = self.last_batch
d("rng_state") = self.rng_state
return d
def load_state_dict(self, state_dict):
self.nan_captured = state_dict.get("nan_captured", False)
if self.nan_captured:
self.last_batch = state_dict("last_batch")
self.rng_state = state_dict("rng_state")
Wichtig ist, dass das Festlegen des Zufallszustands nicht vollständig garantiert wird Reproduzierbarkeit. Die GPU verdankt ihre Macht ihrer massiven Parallelität. In einigen GPU -Operationen können mehrere Threads gleichzeitig an denselben Speicherorten gelesen oder schreiben, was zu Nichtdeterminismus führt. Pytorch ermöglicht eine gewisse Kontrolle darüber über seine use_deterministic_algorithmenDies kann jedoch die Laufzeitleistung beeinflussen. Darüber hinaus besteht die Möglichkeit, dass das NAN -Ereignis nicht reproduziert wird, sobald diese Konfigurationseinstellung geändert wird. Bitte beachten Sie die Pytorch -Dokumentation zu Reproduzierbarkeit Für weitere Particulars.
Zusammenfassung
Die Begegnung mit NAN -Fehlern ist eines der entmutigendsten Ereignisse, die bei der Entwicklung des maschinellen Lernens auftreten können. Diese Fehler verschwenden nicht nur wertvolle Berechnungs- und Entwicklungsressourcen, sondern weisen häufig auf grundlegende Probleme in der Modellarchitektur oder im Experimententwurf hin. Aufgrund ihrer sporadischen, manchmal schwer fassbaren Natur kann das Debuggen von Nan -Misserfolgen ein Albtraum sein.
Dieser Beitrag führte einen proaktiven Ansatz zur Erfassung und Reproduktion von NAN -Fehlern mithilfe eines dedizierten Blitzrufs ein. Die von uns geteilte Lösung ist ein Vorschlag, der für Ihren spezifischen Anwendungsfall geändert und erweitert werden kann.
Diese Lösung kann zwar nicht jedes mögliche Nan -Szenario behandeln, reduziert zwar die Debugging -Zeit, wenn sie zutreffend ist, erheblich und speichert den Entwicklern möglicherweise unzählige Stunden der Frustration und Verschwendung.