Pytorch: RFC: Flag torch.deterministic hinzufügen, um deterministische Algorithmen zu erzwingen

Erstellt am 18. Dez. 2018  ·  67Kommentare  ·  Quelle: pytorch/pytorch

🚀 Funktion

Wir sollten eine globale Variable hinzufügen, um PyTorch zu zwingen, bitweise deterministische Algorithmen zu verwenden. Soumith schlägt vor, das Flag zu einem torch.experimental Unterpaket hinzuzufügen, da wir uns bei einigen Details nicht sicher sind.

Motivation

Der bitweise Determinismus zwischen den Läufen ist manchmal für das Debugging nützlich. Für einige Operationen ist es jedoch schwierig, effiziente deterministische Algorithmen zu schreiben.

Tonhöhe

Wenn torch.experimental.deterministic False (Standard) ist, sollte PyTorch den schnellsten verfügbaren Algorithmus für eine bestimmte Operation verwenden. Wenn torch.experimental.deterministic True , sollte PyTorch nur deterministische Algorithmen verwenden. PyTorch sollte eine Warnung ausgeben, wenn für eine bestimmte Operation kein deterministischer Algorithmus verfügbar ist und torch.experimental.deterministic True .

cuDNN

Wir haben bereits ein torch.backends.cudnn.deterministic Flag, um die Auswahl des cuDNN-Algorithmus zu steuern. Wir sollten dieses Flag vorerst beibehalten und cuDNN auf deterministische Algorithmen beschränken, wenn entweder torch.backends.cudnn.deterministic oder torch.experimental.deterministic True ist.

Nicht-Tore

Wir streben nur einen bitweisen Determinismus zwischen Läufen auf Maschinen mit derselben Architektur und Konfiguration an. Zum Beispiel, auch wenn torch.experimental.deterministic Wahr ist , dass wir nicht darauf abzielen , für bitweise Determinismus , wenn eine der folgenden unterschiedlich:

  • PyTorch-Version
  • CPU-Architektur (zB x86 mit AVX vs. ARM)
  • GPU-Architektur (z. B. AMD vs. NVIDIA oder P100 vs. V100)
  • Bibliotheksabhängigkeiten (zB OpenBLAS vs. MKL)
  • Anzahl der OpenMP-Threads

Umsetzungsvorschläge

Ich schlage vor, diese Funktion in zwei Schritten hinzuzufügen. Der erste Schritt besteht darin, das Flag torch.backends.cudnn.deterministic hinzuzufügen und Warnungen zu allen nicht deterministischen Operationen hinzuzufügen. Der zweite Schritt besteht darin, deterministische Implementierungen für die nicht-deterministischen Operationen hinzuzufügen.

Es gibt eine unvollständige Liste nicht deterministischer Operationen in den

Offene Fragen

Wie sollte torch.experimental.deterministic mit dem RNG-Seed interagieren? Sollte es einen Standard-Seed setzen, wenn kein manueller Seed gesetzt wurde? Soll es eine Warnung ausgeben, wenn kein manueller Seed gesetzt wurde?

cc @ezyang @gchanan @zou3519

feature high priority determinism internals triaged

Hilfreichster Kommentar

Hallo, ich möchte über den zukünftigen Plan für torch.deterministic sprechen. Es gibt ein paar hochrangige Fragen, die wir beantworten müssen:

  1. Was ist die Semantik von torch.deterministic ? Was erwartet der Benutzer? Ist Best Effort für einen Benutzer tatsächlich sinnvoll? Wenn es nicht sinnvoll ist, ist es besser zu definieren torch.deterministic in Hinblick darauf, was Operationen steuert sie ?
  2. Ist es jetzt, da wir das Flag torch.deterministic haben, sinnvoll, das Schlüsselwortargument deterministic= vollständig aus der öffentlich zugänglichen API zu entfernen ( bmm , ich sehe dich an).
  3. Was ist das Endspiel für diese Arbeit? An wie viel davon wirst du (@kurtamohler) arbeiten, im Vergleich zur generischen Community, und wenn wir hier am Ende deines Stints angelangt sind, wie sieht ein vernünftiger Zustand aus?

Beginnend mit (1) heißt es in der aktuellen Dokumentation für fackel.deterministic:

     r"""Sets a global flag to force all operations to use a deterministic
    implementation if available. If an operation that does not have a
    deterministic implementation is called while this setting is True, the
    operation will throw a RuntimeError.

    Note that deterministic operations tend to have worse performance than
    non-deterministic operations.

Dies mag zwar für einen eventuellen Endzustand zutreffen, stellt jedoch ungenau die aktuelle Situation dar, in der viele Operationen nicht geprüft wurden und für ein bestimmtes Modell wissen wir nicht, ob torch.deterministic tatsächlich das tut, was es tut sagt auf der Dose und machen Sie Ihr Modell deterministisch / geben Sie einen Fehler aus, wenn Sie nondet treffen. Im Grunde ist unsere Implementierung in Bezug auf diese Semantik fehlerhaft und wird auf absehbare Zeit weiterhin fehlerhaft sein. Dies ist kein großartiger Zustand.

Wir könnten die Dokumentation von fackel.deterministic ändern, um dies zu verbessern. Einige mögliche Änderungen:

  • Fackel.deterministisch ist beste Bemühung , aber bitte melden Sie Fehler, wenn Sie feststellen, dass es keinen Nicht-Determinismus fängt
  • Torch.deterministic schaltet das Verhalten dieser Operatoren um (und gibt dann eine vollständige Liste der Operatoren an, die es umschaltet)

Der zweite Aufzählungspunkt führt zu (2): Wenn es torch.deterministic jetzt gibt, um den Determinismus umzuschalten, ist es viel weniger wichtig, den Determinismus direkt in der Benutzer-API zu unterstützen. Wir hätten also wahrscheinlich nicht das Argument deterministic zu bmm hinzufügen sollen. Wir könnten erwägen, eine interne Funktion freizugeben, wenn Sie etwas direkt umschalten möchten, aber deterministic sollte nicht direkt in der Funktion selbst verfügbar sein.

Was denkst du? Ich denke, der Wechsel der Dokumente ist wahrscheinlich der einfachste Weg, um auf einen nachhaltigen Weg zu kommen. Es gibt noch einige andere Details, wie zum Beispiel, wie die vollständige Liste ausgefüllt wird, aber diese Semantik ist wahrscheinlich sinnvoller als "ideale" Semantik, die nicht wirklich wahr sein wird.

cc @gchanan @mruberry

Alle 67 Kommentare

Das ist ein Daumen hoch von mir. Das Problem wird in erster Linie darin bestehen, dies tatsächlich überall in der Codebasis auszurollen; nichts Schlimmeres ist zu behaupten, dass wir deterministisch sind, aber insgeheim ist es das nicht :)

Ich bin ganz dafür und mein Ansatz wäre, Operationen und Fehler zu markieren, wenn deterministisch aktiviert ist und wir wissen, dass sie es nicht sind.

Ich denke, Fehler bei nicht-deterministischen Operationen sind zu hart. Warnung scheint eine reibungslosere Erfahrung zu sein

Ich denke, die Standardeinstellung sollte "werfen" sein, aber ich denke, wir könnten dort eine mehrwertige Eigenschaft unterstützen (nicht deterministisch ist in Ordnung, warnen, warnen).

Ich muss zugeben, dass ich den Anwendungsfall einer Warnung nicht wirklich sehe. Wenn die Leute sich für deterministisch genug interessieren, um es einzuschalten, würden sie wahrscheinlich den Fehler erwarten. Sie können es für bestimmte Anrufe immer ausschalten, um zu sagen, dass Sie mit dem Nichtdeterminismus einverstanden sind, der darin enthalten ist.

Fehler, Warnung, ordnungsgemäße Dokumentation...
Letzteres ist ein Muss.
Warnung oder Fehler? Ich gehe mit einem Fehler.

werfen sieht toll aus. Ich stimme Adam zu, dass es vernünftig erscheint, die Möglichkeit zu geben, zu warnen, anstatt zu werfen.

Danke fürs Abwägen. Am Ende ist die Hauptanstrengung für die ternäre Flagge die Flagge selbst, und das ist nicht schwer.
Ich füge Context.h ein Flag hinzu und streue (über eine Dienstprogrammfunktion) AT_ERROR und AT_CHECK.

Hallo,
Gibt es Neuigkeiten zu dieser Flagge?
Determinismus ist entscheidend.
Nach meiner Erfahrung erlaubt die aktuelle Version Determinismus über eine GPU bis zu einer Genauigkeit von 1e-16 unter Verwendung von festen Seeds. Beachten Sie, dass eine infinitesimale Differenz verstärkt werden kann und die Ergebnisse divergieren können.

Bitte bedenken Sie auch den Fall von Multi-GPU (zumindest für ein festes K GPU muss das Verhalten deterministisch sein. Ich kann eine Art Determinismus erreichen, der von Zeit zu Zeit aus einem Grund zusammenbricht I verstehe vorerst nicht (mit Nightly build 1.2.0.dev20190616 ).). Ich kämpfe gerade damit ( 1 , 2 ).

Danke schön!

@t-vi arbeitest du aktiv daran?

Ich möchte Sie nicht davon abhalten, es zu tun.

@t-vi Entschuldigung, wenn ich nicht klar war, ich habe nicht vor, daran zu arbeiten :) . Ich habe nur versucht zu verstehen, ob das jemand aktiv tut.

Nach fast einem Jahr ist das Problem der nichtdeterministischen Interpolation immer noch nicht gelöst.

Hoffe, dass die Community diese Funktion hinzufügt :)

Vielleicht würde eine deterministische Interpolation den Benutzern eine große Hilfe sein.

~ Ich habe es noch nicht wirklich beworben, aber da es anscheinend mehr Benutzerinteresse gibt als Entwicklerressourcen zugewiesen wurden, habe ich dies als Projekt aufgelistet, über das Sie auf meiner Github-Sponsoring-Seite abstimmen können, wenn ich dies einrichte.
Ich bin mir ziemlich sicher, dass wir bis Ende des Jahres gute Fortschritte machen könnten und Interpolation ist sicherlich eines der Dinge, für die ich einen Plan habe, um es zu beheben (ähnlich dem Pseudocode für fold, den ich irgendwo in den Ausgaben finde), aber es ist einfach nicht steht nicht ganz oben auf meiner eigenen Prioritätenliste.~
Hat sich als uninteressant herausgestellt.

eine deterministische Interpolation wird eine große Hilfe sein. Verknüpfung

Stoßpriorität, insbesondere für CUDA, basierend auf Benutzerfeedback

Ich freue mich, dass es behoben wird, danke!

@t-vi Um fair zu sein, denke ich nicht, dass "Stoßpriorität" gleichbedeutend ist mit "es wird repariert" :).

Freue mich auf die Lösungen!

colesbury erwähnte, dass ein Killergrund für deterministische Algorithmen nicht darin besteht, dass der Determinismus tatsächlich das Problem ist, sondern dass Sie ihn ausschließen können, wenn Sie dies einschalten;)

Wie sollte torch.experimental.deterministic mit dem RNG-Seed interagieren? Sollte es einen Standard-Seed setzen, wenn kein manueller Seed gesetzt wurde? Soll es eine Warnung ausgeben, wenn kein manueller Seed gesetzt wurde?

Ich würde vorschlagen, keinen Seed zu setzen, wenn der Benutzer keinen gesetzt hat. Zum einen, weil es zwei Schnittstellen koppelt, die nicht benötigt werden (Benutzer, die sich für Determinismus interessieren, werden RNGs sehr gut verstehen, denke ich). Noch wichtiger ist, dass dies sehr schwer zuverlässig zu tun ist; man kann einen RNG in Multiprozess-/Thread-Anwendungen verwenden, andere torch.Generator Unterklassen haben, auch numpy.random usw.

Ich bin mir bei einer Warnung nicht sicher, nur wenn es einen vernünftigen Ort gibt, um sie zu setzen (zB erzwingen Sie dann, vor determinism=True anstatt in demselben Modul/einer Funktion, in der ein RNG verwendet wird?).

Ich bin nur neugierig, dass der Interpolationsoperator immer noch nicht deterministisch sein kann, wenn ich torch.backends.cudnn.deterministic=True setze. Verwendet die Pytorch-Interpolation nicht cudnn?

Es darf nicht. Sie können Ihren Interpolationslauf mit nvprof überprüfen, um dies zu überprüfen.

Ich frage mich, ob wir weiterhin die deterministic Argumente in Funktionsaufrufen bereitstellen sollten, sobald torch.experimental.deterministic implementiert ist. Vielleicht sollten wir das, weil ein Benutzer für einige Operationen Determinismus und für andere Geschwindigkeiten bevorzugen könnte.

Wenn wir die Argumente beibehalten, was passiert, wenn sich torch.experimental.deterministic und das deterministic Flag einer Funktion gegenüberstehen. Sollte torch.experimental.deterministic = True bedeuten "Verwende Determinismus in allen Fällen, egal was" oder sollte es bedeuten "Verwende Determinismus als Standardwert, aber wenn das deterministic Argument in einem Funktionsaufruf angegeben wird, dann verwende diese Einstellung für diesen speziellen Funktionsaufruf." Mit anderen Worten, wie sollte der folgende Code behandelt werden? Weiß jemand, wie sich das Flag torch.backends.cudnn.deterministic in einer ähnlichen Situation verhält?

torch.experimental.deterministic = True
torch.some_operation(deterministic=False)

@kurtamohler Gute Frage. Ich denke, die einfachste Lösung besteht darin, es zu bool? deterministic=None zu machen und dann None zu interpretieren, dass " torch.experimental.deterministic respektiert" bedeutet und ansonsten genau das zu verwenden, was der Benutzer angefordert hat.

Wir haben eine ähnliche Situation mit der Faltung, aber so wurde es dort gemacht, dass es ein convolution ohne benchmark Argument gibt und dann ein _convolution mit einem expliziten Benchmark.

Ich denke, jede dieser Lösungen wäre akzeptabel; Der Faltungsansatz hat jedoch den zusätzlichen Vorteil, dass das interne deterministic Flag nicht an die für den Benutzer sichtbare API weitergegeben wird (es sei denn, sie verwenden eine interne API).

Was ist der Grund für "Ich möchte überall deterministisch sein, aber _nicht in diesem speziellen Operator_"? Soll dies wirklich ein häufiger Anwendungsfall sein, um vielen unserer Operatoren (und den meisten komplexen) eine zusätzliche Eingabe hinzuzufügen? IMO wäre es besser, Kontextmanager zum Umschalten des Determinismus bereitzustellen.

@apaszke , ja, ich denke, Sie haben Recht, dass es besser wäre, nur Kontextmanager zu verwenden, um den Determinismus deterministic zu irgendwelchen Operatoren hinzufügen sollten, aber einige Operatoren haben es bereits. Wäre es am besten, all diese zu entfernen und BC zu brechen, oder wäre es am besten, sie in der Nähe zu behalten und ihnen zu erlauben, torch.experimental.deterministic zu überschreiben?

Ich würde sagen, dass wir es entfernen oder zumindest privat machen sollten (zB Unterstrich-Präfix oder etw).

Ich frage mich, ob die deterministische Funktion für die Interpolationsfunktion geschlossen ist und nicht implementiert wird?

Nein, wir sind zugänglich für deterministische Versionen ALLER Funktionen in PyTorch

@ezyang welche pytorch-version hat die deterministische F.interpolate-Funktion? ist es ab pytorch 1.6? oder ist es in der neuesten stabilen Version (1.5) verfügbar? oder muss ich Pytorch von der Quelle herunterladen und installieren?

Ich würde mich freuen, daran zu arbeiten

Das obige Commit fügt nur das Flag hinzu, es beeinflusst noch keine Operationen. Ich würde mich freuen, wenn sich jemand ein paar Minuten Zeit nehmen könnte, um es sich anzusehen und mich wissen zu lassen, ob ich etwas falsch gemacht habe oder ob etwas bisher verbessert werden könnte. Ich habe dies auf der Implementierung von torch.backends.cudnn.deterministic basiert.

Das sieht in Ordnung aus, aber ich denke, die interne Benennung sollte keine experimentellen Elemente enthalten (da Sie es angeblich eines Tages nicht experimentell machen möchten, und das sollte nicht bedeuten, dass Sie alle Implementierungsbits umbenennen müssen!)

@ezyang , ja das macht Sinn, ich werde umbenennen.

Ich habe ein torch.experimental.deterministic_error_level hinzugefügt, ähnlich dem, was @t-vi in ​​seiner vorherigen Arbeit zu diesem Thema getan hat. deterministic_error_level steuert das Fehler-/Warnungsverhalten, wenn deterministic == True und eine gegebene Funktion keine deterministische Implementierung hat. Es kann auf 2 (Fehler), 1 (Warnen) oder 0 (Still) eingestellt werden.

Wenn der Benutzer es auf einen anderen Wert festlegt, möchte ich eine abfangbare Python-Laufzeitausnahme auslösen. Normalerweise würde ich TORCH_CHECK() für diese Art von Verhalten verwenden, aber in diesem Fall kann die Ausnahme nicht abgefangen werden, und ich bin mir nicht sicher, warum. Hier ist der TORCH_CHECK() Aufruf: Link

Das passiert, wenn diese Prüfung fehlschlägt:

>>> import torch
>>> try:
...     torch.experimental.deterministic_error_level=50
... except:
...     print('exception caught')
... 
terminate called after throwing an instance of 'c10::Error'
  what():  error level 50 is invalid, must be one of 0: None, 1: Warn, or 2: Error
Exception raised from longToErrorLevel at ../aten/src/ATen/Context.cpp:85 (most recent call first):
frame #0: c10::Error::Error(c10::SourceLocation, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) + 0x58 (0x7f53e2cc0878 in /work/kurtamohler/development/pytorch-deterministic-flag/torch/lib/libc10.so)
frame #1: at::Context::longToErrorLevel(long) + 0x122 (0x7f53f6d61a82 in /work/kurtamohler/development/pytorch-deterministic-flag/torch/lib/libtorch_cpu.so)
frame #2: THPModule_setDeterministicErrorLevel(_object*, _object*) + 0x31 (0x7f53fb5625d1 in /work/kurtamohler/development/pytorch-deterministic-flag/torch/lib/libtorch_python.so)
<omitting python frames>
frame #23: __libc_start_main + 0xe7 (0x7f5432d62b97 in /lib/x86_64-linux-gnu/libc.so.6)

Aborted (core dumped)

Wenn jemand weiß, wie ich das beheben kann, lass es mich wissen.

@kurtamohler THPModule_setDeterministicErrorLevel fehlen HANDLE_TH_ERRORS / END_ HANDLE_TH_ERRORS Makros? Sie werden benötigt, um die C++-Ausnahme abzufangen und in eine Python-Fehlerrückgabe zu übersetzen.

Ah das war es, danke @colesbury!

Ich fange an, allen Anrufern von atomicAdd die nicht-deterministische Warnung hinzuzufügen. Mir ist aufgefallen, dass einige Anrufer nur in bestimmten Fällen atomicAdd verwenden. Zum Beispiel wird adaptive_avg_pool3d_backward nur verwendet, wenn (isizeW%osizeW != 0) || (isizeH%osizeH != 0) || (isizeT%osizeT != 0) wahr ist. Sollte ich nur in diesen Fällen warnen und versuchen, sie in der Fehlermeldung zu vermitteln, oder wäre es in Ordnung, wenn diese Funktionen aufgerufen werden, ob atomicAdd verwendet wird oder nicht?

Es ist wahrscheinlich einfacher zu implementieren und leichter zu verstehen, wenn Sie bedingungslos alarmieren.

@ngimel , ich habe darüber nachgedacht, wie man CUBLAS_WORKSPACE_CONFIG , um eine deterministische Stream-Nutzung sicherzustellen, und ich denke, es gibt zwei Hauptansätze, die in Betracht gezogen werden sollten.

Wenn jemand eine der betroffenen CUDA-Versionen (derzeit 10.2 oder höher) verwendet und torch.set_deterministic(True) aufgerufen wird, verwenden Sie std::getenv , um sicherzustellen, dass CUBLAS_WORKSPACE_CONFIG entweder :16:8 oder :4096:8 . Wenn nicht, führen Sie entweder (1) oder (2) aus:

  1. Einen Fehler ausgeben, der den Benutzer anweist, die Variable entsprechend zu setzen.

  2. Setzen Sie die Variable automatisch mit putenv ( _putenv unter Windows). Damit verbunden sind jedoch einige weitere Gestaltungsentscheidungen. Sollten wir :16:8 (geringere Leistung, aber weniger Speicherverbrauch) oder :4096:8 (höhere Leistung, aber mehr Speicherverbrauch) wählen? Wenn der Benutzer die Variable auf einen anderen nicht deterministischen Wert setzt, müssen wir entweder den ursprünglichen Wert verfolgen und ihn wiederherstellen, wenn torch.set_deterministic(False) aufgerufen wird, oder wir könnten einen Fehler ausgeben, der dem Benutzer mitteilt, dass sie müssen die Variable oder ein anderes Schema aufheben.

Außerdem weiß ich nicht, ob das Setzen der Variablen während der laufenden Anwendung tatsächlich Auswirkungen hat, daher weiß ich nicht genau, ob Option (2) überhaupt möglich ist. Die Variable darf nur einmal überprüft werden, wenn die CUDA-Laufzeit gestartet wird oder wenn ein cuBLAS-Handle erstellt wird. Ich konnte keine Informationen dazu finden, also müsste ich es wahrscheinlich experimentell herausfinden (ich werde einen nicht-deterministischen Stream-Use-Reproduzierer verwenden müssen, um einen Test zu schreiben, also werde ich mir das ansehen). . Ich habe auch nach einem API-Aufruf gesucht, anstatt die Umgebungsvariable zu verwenden, aber CUDA scheint keinen anzubieten.

Haben Sie eine klare Meinung, welche Option besser wäre? Option (2) wäre wahrscheinlich benutzerfreundlicher, aber möglicherweise weniger transparent als Option (1).

Ich weiß nicht, ob das Setzen der Variablen während der laufenden Anwendung tatsächlich Auswirkungen hat

Um dieser Frage nachzugehen, scheint das Festlegen der Umgebungsvariablen in einem Pytorch-Skript den Determinismus des CUDA-Streams nicht zu beeinflussen. Ich habe das Skript von https://github.com/pytorch/pytorch/issues/39849 so modifiziert, dass es mehrmals ausgeführt wird und Trainingsstatistiken vergleicht, um auf nicht deterministisches Verhalten zu prüfen. Es versucht, CUBLAS_WORKSPACE_CONFIG=:4096:8 festzulegen, um eine deterministische Stream-Nutzung sicherzustellen: https://github.com/kurtamohler/pytorch-perf-test-scripts/blob/master/nondeterministic_alert/cuda_stream_nondeterminism.py

Das Ausführen zeigt, dass wir kein deterministisches Verhalten erhalten, wenn die Variable im Skript festgelegt wird:

$ python cuda_stream_nondeterminism.py 
Before setting var: not deterministic
After setting var: not deterministic
After restoring old var: not deterministic

Aber das Ausführen mit der Umgebungsvariablen außerhalb des Skripts macht es deterministisch:

$ CUBLAS_WORKSPACE_CONFIG=:4096:8 python cuda_stream_nondeterminism.py 
Before setting var: possibly deterministic
After setting var: possibly deterministic
After restoring old var: possibly deterministic

Beachten Sie, dass "möglicherweise deterministisch" ausgegeben wird, da ich die Trainingsfunktion nur fünfmal ausführe und es möglich ist, Glück zu haben, auch wenn das Verhalten nicht wirklich deterministisch ist.

Wenn ich den cuda-Stream vielleicht neu initialisieren könnte, würde das ihn vielleicht zwingen, die geänderte CUBLAS_WORKSPACE_CONFIG Variable zu berücksichtigen. Ich würde das gerne versuchen, weiß aber nicht wie oder ob das zur Laufzeit überhaupt möglich ist. Wenn es jemand weiß, lass es mich wissen.

Ich habe herausgefunden, dass ich einen neuen Stream erstellen und verwenden kann mit:

with  torch.cuda.stream(torch.cuda.Stream()):

Der neue Stream berücksichtigt jedoch nicht die geänderte Einstellung der Umgebungsvariablen. Ich habe auch torch.cuda.init() , aber leider ist das ein No-Op, wenn cuda bereits initialisiert wurde.

Wenn uns also nichts anderes einfällt, können wir die Arbeitsbereichskonfiguration wahrscheinlich nicht automatisch ändern, sodass wir möglicherweise nur einen Fehler ausgeben müssen, der den Benutzer auffordert, sie festzulegen.

Ja, das Festlegen der Umgebungsvariablen nach der Initialisierung des cuda-Kontextes hat keine Auswirkung, daher ist es leider eine Alles-oder-Nichts-Lösung. Es klingt vernünftig, einen Fehler auszulösen, der dem Benutzer sagt, dass er es einstellen soll.

Derzeit scheint es nicht möglich zu sein, die CUDA-Version aus einer nicht-nvcc-kompilierten Datei zu überprüfen, also muss ich das zu aten/src/ATen/cuda/detail/CUDAHooks.h hinzufügen (die Überprüfung der cuDNN-Version ist Teil dieser Schnittstelle). . Falls es jemand besser weiß, lass es mich wissen.

Der obige Commit fügt den Fehler hinzu. Aber ich muss jetzt herausfinden, was ich mit den Unit-Tests machen soll. Es gibt zwei Probleme:

  • Um zu testen, ob der Fehler im richtigen Fall geworfen wird (cuda >= 10.2 und CUBLAS_WORKSPACE_CONFIG ist nicht richtig gesetzt), müsste die Testinfrastruktur in der Lage sein, die Umgebungsvariable automatisch zu ändern, bevor ein Test ausgeführt wird
  • Um sicherzustellen, dass die vorhandenen torch.set_deterministic Tests nicht kaputt gehen, müssten wir CUBLAS_WORKSPACE_CONFIG automatisch richtig einstellen. Wir könnten diese Variable möglicherweise einfach standardmäßig in allen CI-Jobs setzen, die cuda >= 10.2 verwenden.

Ich habe herausgefunden, dass ich Umgebungsvariablen aus einem Python-Skript setzen und dann das Fackel-Modul neu laden kann, damit es den neuen Wert berücksichtigt:

>>> import torch
>>> torch.set_deterministic(True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/work/kurtamohler/development/pytorch-deterministic-flag-cuda-env-var/torch/__init__.py", line 306, in set_deterministic
    _C._set_deterministic(d)
RuntimeError: To enable deterministic behavior with CUDA >= 10.2, you must set environment variable CUBLAS_WORKSPACE_CONFIG=:4096:8 or CUBLAS_WORKSPACE_CONFIG=:16:8. For more information, go to https://docs.nvidia.com/cuda/cublas/index.html#cublasApi_reproducibility
>>> import os
>>> os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8'
>>> from importlib import reload
>>> torch = reload(torch)
>>> torch.set_deterministic(True)

Ich weiß nicht, ob das Nachladen der Fackel auch dazu führt, dass CUDA diese Änderung berücksichtigt, aber zumindest gibt uns dies eine Möglichkeit, die Fehlermeldung zu testen. Obwohl ich fragen muss, kann es Probleme beim Nachladen des Brennermoduls innerhalb eines Komponententests geben?

BEARBEITEN: Es stellt sich heraus, dass ich die Fackel nicht neu laden muss, damit sie die geänderte Umgebungsvariable sieht. Auch das Neuladen nach dem Ändern der Variablen hat keinen Einfluss auf die CUDA-Laufzeit.

Das obige Commit adressiert alle Bedenken, die ich in meinem vorherigen Kommentar erwähnt habe. Ich habe einen Decorator hinzugefügt, um jeden API-Test zu umschließen, der torch.set_deterministic() aufruft, wobei CUBLAS_WORKSPACE_CONFIG=:4096:8 nur bei Bedarf vorübergehend festgelegt wird. Außerdem werden das deterministische Flag und die CUBLAS_WORKSPACE_CONFIG-Einstellungen auf den Zustand vor der Ausführung des Tests zurückgesetzt.

Mir wurde klar, dass das Reproduzierbarkeitsdokument erwähnt, dass deterministisches CuDNN-Verhalten Folgendes erfordert:

torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

Weiß jemand in diesem Thread, was benchmark genau ist und warum torch.backends.cudnn.deterministic = True allein nicht ausreicht?

Wir möchten vielleicht erzwingen, dass benchmark deaktiviert wird, wenn torch.is_deterministic() == True . Mit anderen Worten, anstatt ctx.benchmarkCuDNN() direkt an at::_convolution() , sollte es vielleicht ctx.benchmarkCuDNN() && !ctx.deterministic() in dieser Zeile sein: https://github.com/pytorch/pytorch/blob/ master/aten/src/ATen/native/Convolution.cpp#L602

Wenn wir diese Änderung nicht vornehmen, müssen Leute, die set_deterministic und CuDNN verwenden, Folgendes tun:

torch.set_deterministic(True)
torch.backends.cudnn.benchmark = False

Das bedeutet, dass set_deterministic() allein nicht alles abdecken würde, was meiner Meinung nach verwirrend ist.

cc @ezyang @colesbury @t-vi @ngimel

Wenn eine neue Faltungskonfiguration auftritt, führt benchmark=True alle verfügbaren cudnn-Implementierungen aus und wählt eine schnellste aus, wobei die ausgewählte Implementierung zwischengespeichert wird, sodass alle nachfolgenden Faltungsaufrufe mit denselben Parametern diese verwenden. Wenn also deterministic auch auf True die Ergebnisse deterministisch, solange dieser Cache besteht, dh solange Sie sich im selben Prozess befinden. Wenn es Implementierungen mit enger Laufzeit gibt, kann beim nächsten Start des Prozesses und erneutem Benchmarking eine andere Implementierung gewinnen, und die Ergebnisse (obwohl sie im oben beschriebenen Sinne immer noch deterministisch sind) unterscheiden sich vom vorherigen Lauf. Um den Determinismus zwischen den Durchläufen zu gewährleisten, müssen Sie das Benchmarking deaktivieren.

Ich verstehe. Vielleicht ist für einige Anwendungen nur prozessinterner Determinismus, nicht prozessübergreifender Determinismus, von Bedeutung, sodass es nützlich sein könnte, Benchmarking weiterhin verwenden zu können, wenn sie torch.set_deterministic(True) festlegen. In diesem Fall sollte ich das aktuelle Verhalten nicht ändern. Solange ich die Dokumente aktualisiere, um das klarzustellen, sehe ich kein Problem darin.

Ich habe eine Wiki-Seite erstellt, um PyTorch-Mitwirkenden zu helfen, Unterstützung für torch.set_deterministic() hinzuzufügen: https://github.com/pytorch/pytorch/wiki/How-to-support-%60torch.set_deterministic ()%60-in- PyTorch-Operatoren

Alle Verbesserungen sind willkommen.

Außerdem war ich mir nicht sicher, ob der Abschnitt "Derzeit nicht unterstützte Funktionen" in diesem Wiki enthalten sein sollte oder ob er als neues Github-Problem besser wäre (die Wiki-Seite könnte darauf verlinken). Hat jemand eine Präferenz?

Hallo, ich möchte über den zukünftigen Plan für torch.deterministic sprechen. Es gibt ein paar hochrangige Fragen, die wir beantworten müssen:

  1. Was ist die Semantik von torch.deterministic ? Was erwartet der Benutzer? Ist Best Effort für einen Benutzer tatsächlich sinnvoll? Wenn es nicht sinnvoll ist, ist es besser zu definieren torch.deterministic in Hinblick darauf, was Operationen steuert sie ?
  2. Ist es jetzt, da wir das Flag torch.deterministic haben, sinnvoll, das Schlüsselwortargument deterministic= vollständig aus der öffentlich zugänglichen API zu entfernen ( bmm , ich sehe dich an).
  3. Was ist das Endspiel für diese Arbeit? An wie viel davon wirst du (@kurtamohler) arbeiten, im Vergleich zur generischen Community, und wenn wir hier am Ende deines Stints angelangt sind, wie sieht ein vernünftiger Zustand aus?

Beginnend mit (1) heißt es in der aktuellen Dokumentation für fackel.deterministic:

     r"""Sets a global flag to force all operations to use a deterministic
    implementation if available. If an operation that does not have a
    deterministic implementation is called while this setting is True, the
    operation will throw a RuntimeError.

    Note that deterministic operations tend to have worse performance than
    non-deterministic operations.

Dies mag zwar für einen eventuellen Endzustand zutreffen, stellt jedoch ungenau die aktuelle Situation dar, in der viele Operationen nicht geprüft wurden und für ein bestimmtes Modell wissen wir nicht, ob torch.deterministic tatsächlich das tut, was es tut sagt auf der Dose und machen Sie Ihr Modell deterministisch / geben Sie einen Fehler aus, wenn Sie nondet treffen. Im Grunde ist unsere Implementierung in Bezug auf diese Semantik fehlerhaft und wird auf absehbare Zeit weiterhin fehlerhaft sein. Dies ist kein großartiger Zustand.

Wir könnten die Dokumentation von fackel.deterministic ändern, um dies zu verbessern. Einige mögliche Änderungen:

  • Fackel.deterministisch ist beste Bemühung , aber bitte melden Sie Fehler, wenn Sie feststellen, dass es keinen Nicht-Determinismus fängt
  • Torch.deterministic schaltet das Verhalten dieser Operatoren um (und gibt dann eine vollständige Liste der Operatoren an, die es umschaltet)

Der zweite Aufzählungspunkt führt zu (2): Wenn es torch.deterministic jetzt gibt, um den Determinismus umzuschalten, ist es viel weniger wichtig, den Determinismus direkt in der Benutzer-API zu unterstützen. Wir hätten also wahrscheinlich nicht das Argument deterministic zu bmm hinzufügen sollen. Wir könnten erwägen, eine interne Funktion freizugeben, wenn Sie etwas direkt umschalten möchten, aber deterministic sollte nicht direkt in der Funktion selbst verfügbar sein.

Was denkst du? Ich denke, der Wechsel der Dokumente ist wahrscheinlich der einfachste Weg, um auf einen nachhaltigen Weg zu kommen. Es gibt noch einige andere Details, wie zum Beispiel, wie die vollständige Liste ausgefüllt wird, aber diese Semantik ist wahrscheinlich sinnvoller als "ideale" Semantik, die nicht wirklich wahr sein wird.

cc @gchanan @mruberry

@zou3519 schneidet sich auch mit dem Q unter https://github.com/pytorch/pytorch/pull/38683#issuecomment -662590937

Ich freue mich, dass Sie diese Fragen @ezyang , @zou3519 und @mruberry gestellt haben. Ich stimme zu, dass die von mir verfasste Dokumentation eine falsche Darstellung des aktuellen Stands ist.

Mir gefällt die Idee, alle Funktionen, die torch.set_deterministic() betrifft, erschöpfend aufzulisten, damit wir den Benutzer nicht anlügen. Danke, dass du das zu 1.6.0 hinzugefügt hast, @zou3519.

Ich stimme zu, dass wir die Einstellung deterministic als direkte Funktionsargumente anbieten sollten.

Was das Endspiel betrifft, so arbeite ich gerne so lange wie nötig daran, aber es sollte so eingerichtet sein, dass jeder schnell lernen kann, wie man hilft.

Auf lange Sicht denke ich, dass die Bereitstellung einer vollständigen Liste der betroffenen Funktionen eine gültige Entscheidung ist, aber ich glaube nicht, dass eine Strategie allein den Nutzen des deterministischen Flags maximieren würde. Wir können Funktionen (in einer bestimmten Umgebung) wie folgt kategorisieren:

  1. Deterministisch
  2. Standardmäßig nicht deterministisch, unterstützt aber das deterministische Flag (entweder Fehler oder alternative Implementierung)
  3. Nicht deterministisch und unterstützt das deterministische Flag nicht

Der Idealfall ist natürlich, Kategorie 3 vollständig zu eliminieren, und dann würde die Liste der Kategorie-2-Funktionen ausreichen. Funktionen der Kategorie 3 werden jedoch noch für einen beträchtlichen Zeitraum existieren (oder vielleicht für immer, wenn sich nicht alle Mitwirkenden der Frage des Determinismus bewusst sind oder ein Commit versehentlich den Determinismus für eine Funktion entfernt usw.). Selbst wenn wir eine erschöpfende Liste aller Funktionen der Kategorie 2 haben, hat der Benutzer keine einfache Möglichkeit, festzustellen, ob eine Funktion, die nicht in der Liste erscheint, deterministisch ist oder nicht (könnte Kategorie 1 oder 3 sein). Zum Beispiel wird torch.add nicht in der Liste angezeigt, woher weiß der Benutzer also, dass es deterministisch ist?

Vielleicht könnten wir auch darüber nachdenken, eine Liste von Funktionen der Kategorie 3 zu führen. Aber die manuelle Pflege dieser Listen wäre aus vielen Gründen sehr schwierig, daher frage ich mich, ob wir dies etwas automatisieren könnten. Wir könnten möglicherweise einen CI-Job einrichten, der Determinismustests für alle Funktionen ausführt. Es ist nicht möglich, induktiv zu 100 % zu beweisen, dass eine Funktion deterministisch ist, und eine nichtdeterministische Funktion kann manchmal das gleiche Ergebnis mehrmals liefern, wenn wir Pech haben. Aber je öfter wir diese Tests durchführen, desto sicherer können wir sein, zu welcher Kategorie jede Funktion gehört.

Es stellt sich auch die Frage, wie man dem Benutzer am effizientesten alles vermittelt, was wir über jede Funktion und jede Plattform wissen und nicht wissen. Vielleicht könnten wir eine Tabelle mit allen Funktionen der Kategorie 2 und 3 auf jeder Plattform erstellen. Es wäre schön, wenn die Determinismustests automatisch überprüfen könnten, ob diese Tabelle korrekt ist.

Nur Brainstorming, vielleicht sind diese Ideen schwieriger als sie wert sind. Ein pragmatischerer Plan könnte deutlich nachhaltiger sein, wenn auch weniger ideal.

Ist torch.add deterministisch?

import torch
n = 512
device = 'cuda'
a = torch.arange(n**3, device=device, dtype=torch.float32)
a = a.reshape((n, n, n))
b = torch.arange(n**3, device=device, dtype=torch.float32)
b = b.reshape((n, n, n))
out_zero = torch.zeros((n, n, n), device=device)
out_zero = out_zero.set_(out_zero.storage(), storage_offset=0, size=a.size(), stride=(1,1,1))
out_one = torch.zeros((n, n, n), device=device)
out_one = out_one.set_(out_one.storage(), storage_offset=0, size=a.size(), stride=(1,1,1))

torch.add(a, b, out=out_zero)
torch.add(a, b, out=out_one)
(out_zero == out_one).all()
: tensor(False, device='cuda:0')

Wir sollten wahrscheinlich dokumentieren, dass überlappte Tensoren den von uns angestrebten Determinismusvertrag verletzen.

Das Auflisten der Operationen, die von einem "Determinismus"-Flag betroffen sind, klingt gut. Wenn wir jedoch etwas zurücktreten, scheint es, als würden wir wirklich über zwei Dinge sprechen:

  • Anfordern deterministischer Versionen von Operationen, falls verfügbar ( use_deterministic ?)
  • Warnung, wenn eine Operation nicht deterministisch ist

Eine Flagge für die erste Sache scheint einfach. Der zweite ist jedoch etwas schwieriger. Ich befürchte, dass es schwer zu sagen ist, ob die Operationen von mathematischen Bibliotheken wie oneDNN, cuDNN und MAGMA deterministisch sind, insbesondere über Versionen und Hardware hinweg. Hast du eine Idee, wie man das am besten angeht, @kurtamohler? Vielleicht könnten wir bei allen nativen nicht-deterministischen Operationen warnen und auch warnen, wenn Mathe-Bibliotheksaufrufe getätigt wurden? Eine Warnung einmal pro Prozess sollte nicht so aufdringlich sein.

Dieser Ansatz für Warnungen würde die Überprüfung vieler Algorithmen und Aufrufseiten erfordern, bevor er live geht, aber er muss nicht das Flag blockieren, um deterministische Algorithmen auszuwählen, falls diese verfügbar sind.

(Eine dritte Diskussion, die diskutiert wird, ist der beste Weg, die deterministische Algo-Auswahl zu präsentieren (über ein globales Flag oder als Kwargs für Funktionen), aber ich denke, wir können diese Diskussion verzögern, bis wir einen Plan für die Flag(s) festgelegt haben?)

Ich denke, wir sollten das Perfekte hier nicht zum Feind des Guten machen. Ich weiß nicht, wann es 100% sicher war, sich selbst überlappende Tensoren mit PyTorch zu verwenden, und mein Eindruck ist, dass sie nicht von den Normalbürgern verwendet werden.

Mein Eindruck aus den Foren ist, dass die meisten Leute überrascht sind, dass sie etwas zweimal ausführen und dabei unterschiedliche Farbverläufe erhalten, meistens, weil eine der nativen Funktionen von PyTorch atomicAdd verwendet.
Wenn wir dafür Warnungen erhalten, haben wir die meisten Fälle abgedeckt, über die sich die Leute wundern. Etwas, was sich wie die Hälfte anfühlt, stammt tatsächlich vom Hochskalieren nach hinten.

Ich denke, wir sollten klar sagen, dass dies der beste Versuch ist, wenn es um externe Bibliotheken geht, und dass wir Warnungen hinzufügen, wenn wir Probleme erfahren, aber mein Eindruck ist, dass unsere nativen Kernel tatsächlich am wichtigsten sind.

Ich weiß nicht, wann es 100% sicher war, sich selbst überlappende Tensoren mit PyTorch zu verwenden, und mein Eindruck ist, dass sie nicht von den Normalbürgern verwendet werden.

Ja, und alle Programme, die dies tun, können vernünftigerweise als fehlerhaft eingestuft werden. Ich meinte nur, wir sollten darauf achten, den Vertrag, den wir für diese Flaggen haben, zu dokumentieren.

Ich denke, wir sollten klar sagen, dass dies der beste Versuch ist, was externe Bibliotheken betrifft, und dass wir Warnungen hinzufügen, wenn wir Probleme erfahren ...

Das Dokument könnte etwas sagen wie "Mathe-Bibliotheksaufrufe, von denen bekannt ist, dass sie nicht deterministisch sind ..."?

Ich stimme @t-vi zu (und ich mag die Beobachtung, dass die Hälfte des gemeldeten Nichtdeterminismus rückwärts hochskaliert wird). Insbesondere denke ich, dass ein Zustand, in dem wir teilweise dokumentierte Funktionen haben, von denen bekannt ist, dass sie nicht deterministisch sind (oder sogar teilweise dokumentiert sind, dass einige Funktionen deterministisch sind), strikt besser ist als einer, in dem wir überhaupt keine Angaben machen – das Wichtigste ist, nicht zu behaupten, Dinge zu unterstützen, die wir nicht unterstützen! Ich stimme zu, dass es eine nützliche Aktivität ist, darüber nachzudenken, wie man auf Determinismus testen könnte, aber ich denke, dies ist eine orthogonale Aktivität zum Kennzeichnen von APIs, die offensichtlich nicht deterministisch sind.

Da viele Ideen im Umlauf sind, möchte ich mich nur mit meinen spezifischen Gedanken zu einigen von ihnen einmischen:

  1. "Vielleicht könnten wir auch darüber nachdenken, eine Liste von Funktionen der Kategorie 3 zu führen." Das scheint viel Arbeit zu sein. Ich denke, es lohnt sich wahrscheinlich nur für Funktionen, bei denen wir explizit einige Anpassungen für den Determinismus vorgenommen haben (höchstwahrscheinlich Funktionen, die das deterministische Flag unterstützen).
  2. "Wir könnten möglicherweise einen CI-Job einrichten, der Determinismustests für alle Funktionen durchführt." Ich denke, so etwas müsste mit großer Sorgfalt durchgeführt werden, da es von Natur aus auf etwas testet, das nicht deterministisch ist, und das bedeutet, dass der Determinismus-Test selbst "flockig" ist (wird manchmal bestehen und andere nicht bestehen). . Unsere CI-Reporting-Tools kommen mit solchen Situationen nicht so gut zurecht.
  3. "Der zweite ist jedoch etwas kniffliger. Ich mache mir Sorgen, dass es schwer zu sagen ist, ob die Operationen von mathematischen Bibliotheken wie oneDNN, cuDNN und MAGMA deterministisch sind, insbesondere über Versionen und Hardware hinweg." Wir sollten dies am besten tun. In vielen Fällen gibt die Mathematikbibliothek in ihrer Dokumentation ausdrücklich an, ob sie deterministisch sind oder nicht, und wir sollten einfach getreu berichten, was die Dokumente sagen
  4. "Vielleicht könnten wir bei allen nativen nicht-deterministischen Operationen warnen und auch warnen, wenn Mathebibliotheksaufrufe getätigt wurden?" Ich denke nicht, dass wir das tun sollten. Wenn wir vor Nichtdeterminismus warnen, sollte dies daran liegen, dass Nichtdeterminismus IST und nicht, dass er passieren kann. Wenn Sie zu viel warnen, werden die Leute die Warnungen ignorieren.

Ich denke, wir sollten uns nicht um Cross-Version/Hardware-Determinismus kümmern - viel Glück dabei.

Wenn wir vor Nichtdeterminismus warnen, sollte dies daran liegen, dass Nichtdeterminismus IST und nicht, dass er passieren kann. Wenn Sie zu viel warnen, werden die Leute die Warnungen ignorieren.

es scheint schwierig. Was ist zB, wenn ich eine Operation ausführe und die PyTorch-Implementierung deterministisch ist, aber eine Erweiterung etwas überschrieben hat (über einen Dispatch-Schlüssel, eine Fackel-Funktion oder anderes) und ich weiß es jetzt nicht. Wenn das tatsächlich die Quelle meines Nicht-Determinismus ist, scheint das eine Enttäuschung zu sein, nicht gewarnt zu werden.

Wenn das tatsächlich die Quelle meines Nicht-Determinismus ist, scheint das eine Enttäuschung zu sein, nicht gewarnt zu werden.

Klar, aber der User könnte uns auch einfach nicht in seine nichtdeterministischen Spielereien einbeziehen, und dann würde man natürlich nicht damit rechnen, dann gewarnt zu werden ;)

Ich glaube, wir können dieses Problem jetzt schließen, da die Flag-API existiert und gut dokumentiert ist.

@kurtamohler Tolle Arbeit. Danke schön.

Bedeutet das, dass wir torch.manual_seed(111) könnten, um alles deterministische festzulegen, einschließlich der Operation interpolation ?

Nein. Sehen Sie sich den Hinweis zur Reproduzierbarkeit / Zufälligkeit an .
Bisher haben wir eine Infrastruktur, die bekannten Quellen zum Nicht-Determinismus markiert und eine stark verbesserte Dokumentation, damit Sie wissen, was vor sich geht.
Wenn Sie nicht-deterministische Operationen treffen, haben Sie immer noch kein Glück, aber jetzt ist es vernünftiger, daran zu arbeiten.

Insbesondere die Interpolation scheint etwas, das deterministisch gemacht werden könnte, indem man einen nicht allzu komplizierten Kernel für die Rückwärtsbewegung schreibt.

@t-vi Hallo, jetzt, da pytorch 1.7 veröffentlicht wurde, wurde der Interpolations-Rückwärts-Kernel aktualisiert?

Die CUDA-Upsampling-Kernel und rückwärts liegen also in aten/src/ATen/native/cuda/UpSample* . Ein grep legt nahe, dass linear, bilinear, kubisch nichtdeterministische Rückwärtsrichtungen haben (sie haben eine Warnmarkierung), die nächsten jedoch nicht.
@kurtamohler wäre jedoch die viel bessere Person, um zu fragen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

eliabruni picture eliabruni  ·  3Kommentare

a1363901216 picture a1363901216  ·  3Kommentare

bartolsthoorn picture bartolsthoorn  ·  3Kommentare

mishraswapnil picture mishraswapnil  ·  3Kommentare

negrinho picture negrinho  ·  3Kommentare