Wie man aus einem hohen IO-Datensatz in Pytorch liest, der von Epoche zu Epoche wächst

8

Ich verwende Tensorflow, schreibe jedoch eine Dokumentation für Benutzer, die in der Regel je nach Deep-Learning-Framework variiert .

Wenn ich mit Datensätzen arbeite, die nicht in das lokale Dateisystem (TB +) passen, probiere ich Daten aus einem entfernten Datenspeicher und schreibe Proben lokal in ein Tensorflow-Standardformat tfrecords.

Während der ersten Trainingsepoche habe ich nur einige Werte abgetastet, daher ist eine Epoche lokaler Daten sehr klein, ich trainiere darauf. In Epoche 2 überprüfe ich erneut, welche Datendateien von meinen Stichproben-Teilprozessen erzeugt wurden (jetzt mehr), und trainiere den erweiterten Satz lokaler Datendateien für die nächste Epoche. Wiederholen Sie den Vorgang in jeder Epoche. Auf diese Weise baue ich einen lokalen Cache mit Samples auf und kann ältere Samples entfernen, wenn ich den lokalen Speicher auffülle. Der lokale Stichproben-Cache wächst ungefähr zu dem Zeitpunkt, an dem das Modell die Varianz am meisten benötigt (in Richtung des letzten Teils des Trainings).

In Python / Tensorflow ist es entscheidend, dass ich die Daten im Python-Trainingsschleifenprozess nicht deserialisiere, da die Python-GIL die Datenübertragungsraten (300-600 MB / s, die Daten sind wissenschaftlich unkomprimierbar) und damit die GPU-Leistung nicht unterstützen kann leidet, wenn die Python GIL die Trainingsschleife nicht schnell bedienen kann.

Durch das Schreiben der Samples in eine tfrecordsDatei aus Unterprozessen (Python-Multiprocessing) kann der native Tensorflow eine TFRecordsDatasetDeserialisierung außerhalb von Python durchführen. Daher umgehen wir die Python-GIL-Probleme und können eine GPU mit hohen E / A-Datenraten sättigen.

Ich würde gerne wissen, wie ich dieses Problem in Pytorch angehen würde. Ich schreibe über die verwendete Stichprobenstrategie und möchte Benutzern von Tensorflow und PyTorch spezifische Empfehlungen geben, aber ich kenne das PyTorch-Vorverarbeitungs-Ökosystem nicht gut genug, um mit ausreichenden Details zu schreiben.

Randnotiz: Die einzige rein Python-basierte Lösung zur Unterstützung dieser Datenübertragungsraten ist möglicherweise Python 3.8 mit gemeinsam genutztem System V-Speicher und Multiprocessing. Ich habe dies jedoch noch nicht versucht, da die Unterstützung dafür nicht ausreicht (bald wird es so sein) ). Bestehende Multiprozessor-Lösungen reichen nicht aus, da sie eine Deserialisierung im Trainingsschleifenprozess erfordern und somit die GIL während der Deserialisierung mit hohen E / A-Raten sperren.

David Parks
quelle
2
Woher wissen Sie, dass Datenübertragungsraten unter Python GIL leiden? Meines Wissens ist der CPU-gebundene Betrieb in den meisten Fällen von GIL betroffen, nicht der E / A-gebundene Betrieb.
Bomben
Wenn ich in meinen Tests nur die Deserialisierung zwischen Python-Prozessen mit den schnellsten Datenraten durchführe, die ich erreichen kann, bleibt der Zielprozess bei 100% CPU-Auslastung. Ich habe viele Ansätze versucht, Asyncio, Multiprocessing, sogar direkte Socket-Lesevorgänge. Bei direkten Socket-Lesevorgängen kann ich prozessübergreifend 4 GB / s erreichen. In dem Moment, in dem ich sogar versuche, Binärzeichenfolgen zu verbinden, sinke ich auf 2 GB / s, und alles Komplexere senkt mich auf eine maximale xfer-Rate von etwa 1 GB / s. Das ist alles, wenn der Zielprozess den Kern vollständig ausnutzt und somit die GIL sperrt.
David Parks
Beachten Sie, dass dies bei gängigen großen Datensätzen wie Imagenet kein wirkliches Problem darstellt, da die zum Verschieben komprimierter JPEGs in großen neuronalen Netzen erforderliche E / A im Vergleich zu den Anforderungen an unkomprimiertes wissenschaftliches Datentraining in kleinen Netzen gering ist.
David Parks
1
Eine Zeichenfolgenverknüpfung wird in eine CPU-gebundene Operation eingeteilt und kann leicht eine CPU-Kapazität von 100% erfordern, ohne die E / A-Kapazität der Maschine überhaupt zu nutzen. Es ist also kein Beweis dafür, dass eine GIL den E / A-Durchsatz einschränkt.
Bomben
2
Diese trivialen Operationen beanspruchen nicht die GIL des Hauptprozesses, wenn die Daten DataLoaderwie in meiner Antwort geladen werden .
Bomben

Antworten:

7

Tatsächlich können Sie Daten in einem Unterprozess mithilfe von einfach deserialisieren torch.utils.data.DataLoader. Wenn Sie das num_workersArgument auf 1 oder einen höheren Wert setzen, können Sie Unterprozesse mit ihren eigenen Python-Interpreten und GILs erzeugen.

loader = torch.utils.data.DataLoader(your_dataset, num_workers=n, **kwargs)
for epoch in range(epochs):
    for batch_idx, data in enumerate(loader):
         # loader in the main process does not claim GIL at this point

A Dataloaderbenötigt a torch.utils.data.Dataset, um Daten von zu erhalten. Es ist möglicherweise keine triviale Aufgabe, in Ihrem Fall eine geeignete Unterklasse zu implementieren. Falls Sie Datasetfür jede Epoche eine Instanz neu erstellen müssen , können Sie so etwas tun.

for epcoh in range(epochs):
    dset = get_new_dataset()
    loader = torch.utils.data.DataLoader(dset, num_workers=n, **kwargs)
    for batch_idx, data in enumerate(loader):
        # Do training

oder noch besser

dset = get_new_dataset()
loader = torch.utils.data.DataLoader(dset, num_workers=n, **kwargs)

for epcoh in range(epochs):
    last_batch_idx =  (len(dset)-1) // loader.batch_size
    for batch_idx, data in enumerate(loader):
        # Prepare next loader in advance to avoid blocking
        if batch_idx == last_batch_idx:
            dset = get_new_dataset()
            loader = torch.utils.data.DataLoader(dset, num_workers=n, **kwargs)
        # Do training

Als Randnotiz beachten Sie bitte, dass es sich in den meisten Fällen um einen CPU-gebundenen Betrieb handelt, der von GIL betroffen ist, und nicht um einen E / A-gebundenen Betrieb, dh threadingfür einen rein E / A-schweren Betrieb, den Sie nicht einmal benötigen subprocess. Weitere Informationen finden Sie in dieser Frage und in diesem Wikipedia- Artikel .

Bomben
quelle
torch.utils.data.DataLoaderPlatzieren Sie zur Bestätigung Daten aus den Unterprozessen auf der GPU oder versuchen Sie, die Multiprozession von Python zu verwenden, um sie in den Trainingsschleifenprozess zu verschieben? Ich habe festgestellt, dass nur die Deserialisierung von einem Prozess zum anderen bei Datenraten nahe 1 GB / s> 1 Kern der Arbeit ist, daher die GIL-Probleme, auf die ich gestoßen bin, als ich diesen Ansatz in TF ausprobiert habe. Wenn torch.utils.data.DataLoaderDaten jedoch so auf die GPU verschoben werden, dass keine Python-Deserialisierung erforderlich ist, ist alles in Ordnung. Ich möchte dieses Verständnis nur bestätigen.
David Parks
@DavidParks Welche spezifische Funktion verwenden Sie, wenn Sie die Deserialisierung von einem Prozess zum anderen testen? Anscheinend beinhaltet der Deserialisierungsprozess eine CPU-gebundene Operation, daher die GIL-Probleme.
Bomben
Ich habe Multiprocessing (sehr langsam), Pipes (besser) und Raw Socket Reads (am besten) ausprobiert. Keine dieser Funktionen funktioniert, wenn die E / A-Raten einen wesentlichen Bruchteil von GB / s ausmachen und nur so viele Daten verschoben werden, dass mehr als ein Kern erforderlich ist. Daher fallen Python-Lösungen (vor 3.8 und gemeinsamem Speicher von System V) für mich in Tensorflow auseinander. Deshalb schreibe ich in tfrecords-Dateien und lasse Tensorflow außerhalb von Python deserialisieren. TF sperrt die Python-GIL nicht und parallelisiert die Vorgänge. Daher verwendet mein Hauptprozess 600% CPU, während die Python-GIL inaktiv und frei bleibt, um die Trainingsschleife zu bedienen.
David Parks
@DavidParks Ich meine, welche Art von Deserialisierungsfunktion oder Bibliothek verwenden Sie? (keine Interprozess-Kommunikationsbibliothek). torch.utils.data.DataLoaderkann leicht 600% CPU oder mehr ausnutzen, und der Hauptprozess benötigt in den meisten Fällen nicht viel CPU-Leistung, wenn das Training hauptsächlich aus GPU-Berechnungen besteht (wenn das Training hauptsächlich aus CPU-Berechnungen besteht, ist dies immer noch kein Problem, da die Matrixoperation von pytorch problemlos mehrere verwenden kann CPUs).
Bomben
Verwenden Sie nur Pickle, um über Python-Prozesse hinweg zu deserialisieren, und dann eine Python-Generatorfunktion, um Proben in das TensorFlow-Ökosystem einzuspeisen. Das ist der Ansatz, der bei mir fehlschlägt. Sobald die Daten im TensorFlow-Ökosystem gespeichert sind, werden sie auf der GPU abgelegt, und das Training ist eine andere Geschichte. TF bietet Python-Unterprozessen keine Möglichkeit, Daten an TF weiterzuleiten. Sie haben nur wenige Optionen, und tfrecords-formatierte Daten (Protokollpuffer-Format) sind am logischsten. Es hört sich so an, als ob es in PyTorch einfacher wäre, also werde ich einige PyTorch-Benutzer hier bitten, es zu validieren.
David Parks