Pytorch: RFC : ajouter le drapeau torch.deterministic pour forcer les algorithmes déterministes

Créé le 18 déc. 2018  ·  67Commentaires  ·  Source: pytorch/pytorch

Caractéristique

Nous devrions ajouter une variable globale pour forcer PyTorch à utiliser des algorithmes déterministes au niveau du bit. Soumith suggère d'ajouter le drapeau à un sous-package torch.experimental car nous ne sommes pas sûrs de certains détails.

Motivation

Le déterminisme au niveau du bit entre les exécutions est parfois utile pour le débogage. Cependant, il est difficile d'écrire des algorithmes déterministes efficaces pour certaines opérations.

Terrain

Lorsque torch.experimental.deterministic est False (valeur par défaut), PyTorch doit utiliser l'algorithme le plus rapide disponible pour une opération donnée. Lorsque torch.experimental.deterministic vaut True , PyTorch ne doit utiliser que des algorithmes déterministes. PyTorch devrait émettre un avertissement si nous n'avons pas d'algorithme déterministe disponible pour une opération donnée et que torch.experimental.deterministic est True .

cuDNN

Nous avons déjà un indicateur torch.backends.cudnn.deterministic pour contrôler les choix de l'algorithme cuDNN. Nous devrions garder ce drapeau pour le moment et restreindre cuDNN aux algorithmes déterministes si torch.backends.cudnn.deterministic ou torch.experimental.deterministic est Vrai.

Non-buts

Nous visons uniquement un déterminisme au niveau du bit entre les exécutions sur des machines avec la même architecture et la même configuration. Par exemple, même lorsque torch.experimental.deterministic est True, nous ne visons pas le déterminisme au niveau du bit lorsque l'un des éléments suivants varie :

  • Version PyTorch
  • Architecture CPU (par exemple x86 avec AVX vs ARM)
  • Architecture GPU (par exemple AMD vs NVIDIA ou P100 vs V100)
  • Dépendances de la bibliothèque (par exemple OpenBLAS vs MKL)
  • Nombre de threads OpenMP

Suggestions de mise en œuvre

Je suggère d'ajouter cette fonctionnalité en deux étapes. La première étape consiste à ajouter le drapeau torch.backends.cudnn.deterministic et à ajouter des avertissements à toutes les opérations non déterministes. La deuxième étape consiste à ajouter des implémentations déterministes pour les opérations non déterministes.

Il existe une liste partielle des opérations non déterministes dans la documentation PyTorch .

Questions ouvertes

Comment torch.experimental.deterministic interagir avec la graine RNG ? Doit-il définir une valeur par défaut si aucune valeur manuelle n'a été définie ? Doit-il émettre un avertissement si aucune semence manuelle n'a été définie ?

cc @ezyang @gchanan @zou3519

feature high priority determinism internals triaged

Commentaire le plus utile

Bonjour, je veux parler du plan à venir pour torch.deterministic . Il y a quelques questions de haut niveau auxquelles nous devons répondre :

  1. Quelle est la sémantique de torch.deterministic ? Qu'attend l'utilisateur ? Le meilleur effort est-il réellement utile pour un utilisateur ? Si ce n'est pas utile, est - il préférable de définir torch.deterministic en termes de ce que les opérations qu'il contrôle?
  2. Maintenant que nous avons le drapeau torch.deterministic , est-il logique d'éliminer entièrement l'argument de mot-clé deterministic= de l'API publique ( bmm , je vous regarde).
  3. Quel est le jeu final de ce travail ? Sur quelle partie (@kurtamohler) allez-vous travailler, par rapport à la communauté générique, et quand nous arriverons à la fin de votre séjour ici, à quoi ressemble un état raisonnable ?

À partir de (1), la documentation actuelle de torch.deterministic dit :

     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.

Bien que cela puisse être vrai pour un état final éventuel, cela représente de manière inexacte la situation actuelle, où de nombreuses opérations n'ont pas été auditées et pour un modèle donné, nous ne savons pas si torch.deterministic fera réellement ce qu'il dit sur l'étain et rendez votre modèle déterministe/générez une erreur lorsque vous appuyez sur nondet. Donc, fondamentalement, notre implémentation est boguée par rapport à cette sémantique, et continuera de l'être dans un avenir prévisible. Ce n'est pas un bon état dans lequel être.

Nous pourrions modifier la documentation de torch.deterministic pour améliorer cela. Quelques changements possibles :

  • torch.deterministic est le meilleur effort , mais veuillez signaler les bogues si vous voyez qu'il n'attrape pas un certain non-déterminisme
  • torch.deterministic bascule le comportement de ces opérateurs (et donne ensuite une liste exhaustive des opérateurs qu'il bascule)

Le deuxième point mène à (2) : si torch.deterministic existe désormais comme moyen de basculer le déterminisme, il est beaucoup moins important de prendre en charge le déterminisme directement dans l'API utilisateur. Nous n'aurions donc probablement pas dû ajouter l'argument deterministic à bmm. Nous pourrions envisager d'exposer une fonction interne si vous souhaitez basculer quelque chose directement, mais deterministic ne devrait pas être disponible directement sur la fonction elle-même.

Qu'en penses-tu? Je pense que changer les documents est probablement le moyen le plus simple de s'engager sur une voie durable. Il y a d'autres détails, comme comment remplir la liste exhaustive, mais cette sémantique a probablement plus de sens que la sémantique "idéale" qui ne sera pas vraie.

cc @gchanan @mruberry

Tous les 67 commentaires

C'est un coup de pouce de ma part. Le problème sera principalement de savoir comment déployer cela partout dans la base de code ; rien de pire est de prétendre que nous sommes déterministes, mais secrètement ce n'est pas le cas :)

Je suis tout à fait d'accord et mon approche serait de signaler les opérations et les erreurs lorsque le déterminisme est activé et que nous savons qu'ils ne le sont pas.

Je pense que l'erreur sur les opérations non déterministes est trop sévère. L'avertissement semble être une expérience plus fluide

Je pense que la valeur par défaut devrait être de jeter, mais je suppose que nous pourrions y prendre en charge une propriété à valeurs multiples (non déterministe est ok, avertir, jeter).

Je dois admettre que je ne vois pas vraiment le cas d'utilisation d'un avertissement. Lorsque les gens se soucient suffisamment du déterminisme pour l'activer, ils s'attendraient probablement à l'erreur. Vous pouvez toujours le désactiver pour certains appels pour dire que vous êtes d'accord avec le non-déterminisme qui s'y trouve.

Erreur, avertissement, documentation appropriée...
Ce dernier est un incontournable.
Avertissement ou erreur ? Je vais y aller avec une erreur.

le lancer semble génial. Je suis d'accord avec Adam que donner une option pour avertir au lieu de lancer semble raisonnable.

Merci pour votre contribution. Au final, le principal effort pour le drapeau ternaire est le drapeau lui-même, et ce n'est pas difficile.
Je vais ajouter un indicateur à Context.h et saupoudrer (via une fonction utilitaire) AT_ERROR et AT_CHECK.

Bonjour,
Des nouvelles de ce drapeau ?
Le déterminisme est crucial.
D'après mon expérience, la version actuelle permet le déterminisme sur un GPU, jusqu'à une précision de 1e-16 , en utilisant des valeurs fixes. Notez que la différence infinitésimale peut être amplifiée et faire diverger les résultats.

S'il vous plaît, considérez également le cas du multigpu (au moins pour un K gpus fixe, le comportement doit être déterministe. Je suis capable d'obtenir une sorte de déterminisme qui tombe en panne de temps en temps pour une raison que je je ne comprends pas pour le moment (en utilisant nightly build 1.2.0.dev20190616 ).). J'ai du mal avec ça en ce moment ( 1 , 2 ).

Merci!

@t-vi travaillez-vous activement dessus ?

Je ne veux pas t'empêcher de le faire.

@t-vi Désolé si je n'ai pas été clair, je ne prévois pas de travailler dessus :) . J'essayais juste de comprendre si quelqu'un le faisait activement.

Après près d'un an, le problème de l'interpolation non déterministe n'est toujours pas résolu.

J'espère que la communauté ajoutera cette fonctionnalité :)

Peut-être qu'une interpolation déterministe apporterait une grande aide aux utilisateurs.

~ Je ne l'ai pas encore vraiment annoncé, mais étant donné qu'il semble y avoir plus d'intérêt des utilisateurs que de ressources de développement allouées, je l'ai répertorié comme un projet sur lequel vous pouvez voter sur ma page de parrainage github lorsque je l'ai configuré.
Je suis tout à fait certain que nous pourrions faire de bons progrès d'ici la fin de l'année et l'interpolation est certainement l'une des choses que j'ai un plan pour corriger (similaire au pseudo-code pour le pli que je suis quelque part dans les problèmes) mais n'est pas haut sur ma propre liste de priorités.~
S'est avéré être pas intéressant.

une interpolation déterministe sera d'une grande aide. lien

Priorité de supplantation, en particulier pour CUDA, basée sur les commentaires des utilisateurs

Je suis content que ce soit réglé, merci !

@t-vi pour être juste, je ne pense pas que "priorité de remplacement" équivaut à "c'est en cours de correction" :).

J'attends les solutions avec impatience!

colesbury a mentionné qu'une raison majeure des algorithmes déterministes n'est pas parce que le déterminisme est en fait le problème, mais que vous pouvez l'exclure lorsque vous l'activez ;)

Comment torch.experimental.deterministic interagir avec la graine RNG ? Doit-il définir une valeur par défaut si aucune valeur manuelle n'a été définie ? Doit-il émettre un avertissement si aucune semence manuelle n'a été définie ?

Je suggérerais de ne pas définir une graine si aucune n'a été définie par l'utilisateur. D'une part, car il couple deux interfaces qui ne sont pas nécessaires (les utilisateurs soucieux du déterminisme comprendront très bien les RNG, je pense). Plus important encore, c'est très difficile à faire de manière fiable ; on peut utiliser un RNG dans des applications multi-processus/threadées, avoir d'autres sous-classes torch.Generator , utiliser également numpy.random , etc.

Pas sûr d'un avertissement, seulement s'il y a un endroit sain pour le définir (par exemple, forcez-vous alors à semer avant determinism=True plutôt que dans le même module/fonction où un RNG est utilisé ?).

Je suis juste curieux de constater que lorsque je définis torch.backends.cudnn.deterministic=True , l'opérateur d'interpolation ne peut toujours pas être déterministe. L'interpolation pytorch n'utilise-t-elle pas cudnn ?

Ce n'est peut-être pas le cas. Vous pouvez nvprofer votre exécution d'interpolation pour vérifier avec certitude.

Je me demande si nous devrions ou non continuer à fournir les arguments deterministic dans les appels de fonction une fois que torch.experimental.deterministic est implémenté. Peut-être que nous devrions, car un utilisateur peut préférer le déterminisme pour certaines opérations et la vitesse pour d'autres.

Si nous gardons les arguments, que se passe-t-il si torch.experimental.deterministic et le drapeau deterministic une fonction s'opposent. Si torch.experimental.deterministic = True signifie "utiliser le déterminisme dans tous les cas, quoi qu'il arrive", ou devrait-il signifier "utiliser le déterminisme comme valeur par défaut, mais si l'argument deterministic est spécifié dans un appel de fonction, alors utilisez ce paramètre pour cet appel de fonction spécifique." En d'autres termes, comment le code ci-dessous doit-il être traité ? Est-ce que quelqu'un sait comment le drapeau torch.backends.cudnn.deterministic agit dans une situation similaire ?

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

@kurtamohler Bonne question. Je pense que la solution la plus simple est de le faire bool? deterministic=None , puis d'interpréter None comme signifiant "respecter torch.experimental.deterministic ", et sinon d'utiliser exactement ce que l'utilisateur a demandé.

Nous avons en quelque sorte une situation similaire avec la convolution, mais la façon dont cela a été fait était qu'il y avait un convolution sans argument benchmark , puis un _convolution avec un argument explicite référence.

Je pense que l'une ou l'autre de ces solutions serait acceptable; cependant, l'approche de convolution a l'avantage supplémentaire de ne pas divulguer l'indicateur interne deterministic à l'API visible par l'utilisateur (à moins qu'ils n'utilisent une API interne).

Quelle est la justification de « Je veux être déterministe partout, mais _pas dans cet opérateur particulier_ » ? Est-ce vraiment censé être un cas d'utilisation suffisamment courant pour justifier l'ajout d'une entrée supplémentaire à bon nombre de nos opérateurs (et à la plupart des opérateurs complexes) ? OMI, il serait préférable de fournir des gestionnaires de contexte pour basculer le déterminisme.

@apaszke , oui, je pense que vous avez raison de dire qu'il serait préférable d'utiliser simplement des gestionnaires de contexte pour basculer le déterminisme. Je ne dirais pas que nous devrions ajouter l'argument deterministic à n'importe quel opérateur, mais certains opérateurs l'ont déjà. Serait-il préférable de supprimer tous ces éléments et de casser BC, ou serait-il préférable de les conserver et de leur permettre de remplacer torch.experimental.deterministic ?

Je dirais que nous devrions le supprimer ou le rendre privé au moins (c'est-à-dire le préfixe de soulignement ou qc).

Je me demande si la fonctionnalité déterministe pour la fonction d'interpolation est fermée et ne sera pas implémentée ?

Non, nous sommes favorables aux versions déterministes de TOUTES les fonctions dans PyTorch

@ezyang quelle version de pytorch a la fonction déterministe F.interpolate ? est-ce à partir de pytorch 1.6? ou est-il disponible dans la dernière version stable (1.5) ? ou dois-je télécharger et installer Pytorch à partir des sources ?

Je serais heureux de commencer à travailler là-dessus

Le commit ci-dessus n'ajoute que le drapeau, il n'affecte pas encore les opérations. J'apprécierais si quelqu'un pouvait prendre quelques minutes pour le regarder et me faire savoir si j'ai fait quelque chose de mal ou si quelque chose pourrait être amélioré jusqu'à présent. Je me suis basé sur la façon dont torch.backends.cudnn.deterministic est implémenté.

Cela semble OK, mais j'ai l'impression que le nommage interne ne devrait pas inclure expérimental (puisque, apparemment, un jour vous voulez le rendre non expérimental, et cela ne devrait pas impliquer d'avoir à renommer tous les bits d'implémentation !)

@ezyang , ouais ça a du sens, je vais renommer.

J'ai ajouté un torch.experimental.deterministic_error_level , similaire à ce que @t-vi a fait dans ses précédents travaux sur ce problème. deterministic_error_level contrôle le comportement d'erreur/avertissement si deterministic == True et une fonction donnée n'a pas d'implémentation déterministe. Il peut être défini sur 2 (erreur), 1 (avertissement) ou 0 (silencieux).

Si l'utilisateur le définit sur une autre valeur, je souhaite lancer une exception d'exécution python capturable. Habituellement, j'utiliserais TORCH_CHECK() pour ce genre de comportement, mais dans ce cas, l'exception n'est pas interceptable et je ne sais pas pourquoi. Voici l'appel TORCH_CHECK() : lien

Voici ce qui se passe lorsque cette vérification échoue :

>>> 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)

Si quelqu'un sait comment je peux y remédier, merci de me le faire savoir.

@kurtamohler manque les macros THPModule_setDeterministicErrorLevel HANDLE_TH_ERRORS / END_ HANDLE_TH_ERRORS ? Ils sont nécessaires pour intercepter l'exception C++ et la traduire en un retour d'erreur Python.

Ah c'était ça, merci @colesbury !

Je commence à ajouter l'alerte non déterministe à tous les appelants de atomicAdd . J'ai remarqué que certains appelants n'utilisent atomicAdd dans certains cas. Par exemple, adaptive_avg_pool3d_backward n'est utilisé que si (isizeW%osizeW != 0) || (isizeH%osizeH != 0) || (isizeT%osizeT != 0) est vrai. Dois-je alerter uniquement dans ces cas et essayer de les transmettre dans le message d'erreur, ou serait-il correct de simplement alerter chaque fois que ces fonctions sont appelées, que atomicAdd soit utilisé ou non ?

C'est probablement plus facile à mettre en œuvre et plus facile à comprendre si vous alertez inconditionnellement.

@ngimel , j'ai réfléchi à la façon d'utiliser CUBLAS_WORKSPACE_CONFIG pour assurer une utilisation déterministe du flux, et je pense qu'il y a deux approches principales à considérer.

Si quelqu'un utilise l'une des versions de CUDA affectées (10.2 ou supérieure pour le moment) et que torch.set_deterministic(True) est appelé, utilisez std::getenv pour vous assurer que CUBLAS_WORKSPACE_CONFIG est soit :16:8 ou :4096:8 . Sinon, faites (1) ou (2) :

  1. Renvoyez une erreur indiquant à l'utilisateur de définir la variable de manière appropriée.

  2. Définissez automatiquement la variable avec putenv ( _putenv sous Windows). Cependant, d'autres décisions de conception sont associées à cela. Devrions-nous choisir :16:8 (moins de performances, mais moins d'utilisation de la mémoire) ou :4096:8 (plus de performances, mais plus d'utilisation de la mémoire) ? De plus, si l'utilisateur définit la variable sur une autre valeur non déterministe, nous devrons soit garder une trace de la valeur d'origine et la restaurer si torch.set_deterministic(False) est appelé, soit générer une erreur indiquant à l'utilisateur que ils doivent supprimer la variable ou un autre schéma.

De plus, je ne sais pas si la définition de la variable alors que l'application est déjà en cours d'exécution aura réellement un effet, donc je ne sais pas avec certitude si l'option (2) est même possible. La variable ne peut être vérifiée qu'une seule fois, au démarrage de l'environnement d'exécution CUDA ou lors de la création d'un handle cuBLAS. Je n'ai pas pu trouver d'informations à ce sujet, donc je devrais probablement le découvrir expérimentalement (je vais devoir utiliser un reproducteur d'utilisation de flux non déterministe pour écrire un test de toute façon, donc je vais me pencher là-dessus) . J'ai également recherché un appel d'API, plutôt que d'utiliser la variable d'environnement, mais CUDA ne semble pas en proposer un.

Avez-vous une opinion bien arrêtée sur l'option qui serait la meilleure? L'option (2) serait probablement plus conviviale, mais peut-être moins transparente que l'option (1).

Je ne sais pas si la définition de la variable alors que l'application est déjà en cours d'exécution aura réellement un effet

Pour donner suite à cette question, la définition de la variable d'environnement dans un script pytorch ne semble pas affecter le déterminisme du flux CUDA. J'ai modifié le script de https://github.com/pytorch/pytorch/issues/39849 pour l'exécuter plusieurs fois et comparer les statistiques d'entraînement pour vérifier le comportement non déterministe. Il essaie de définir CUBLAS_WORKSPACE_CONFIG=:4096:8 pour assurer une utilisation déterministe du flux : https://github.com/kurtamohler/pytorch-perf-test-scripts/blob/master/nondeterministic_alert/cuda_stream_nondeterminism.py

L'exécuter montre que nous n'obtenons pas de comportement déterministe en définissant la variable dans le script :

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

Mais l'exécuter avec la variable d'environnement définie en dehors du script le rend déterministe :

$ 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

Notez qu'il affiche "peut-être déterministe" car je n'exécute la fonction d'entraînement que 5 fois, et il est possible d'avoir de la chance même si le comportement n'est pas vraiment déterministe.

Peut-être que si je pouvais réinitialiser le flux cuda, cela le forcerait à honorer la variable CUBLAS_WORKSPACE_CONFIG modifiée. J'aimerais essayer ça, mais je ne sais pas comment ou s'il est même possible de le faire au moment de l'exécution. Si quelqu'un sait, merci de me le faire savoir.

J'ai découvert que je pouvais créer et utiliser un nouveau flux avec :

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

Mais le nouveau flux ne respecte pas le paramètre de variable d'environnement modifié. J'ai également trouvé torch.cuda.init() , mais malheureusement, ce n'est pas possible si cuda a déjà été initialisé.

Donc, à moins que nous ne puissions penser à autre chose à essayer, il semble que nous ne puissions probablement pas modifier la configuration de l'espace de travail automatiquement, nous devrons donc peut-être simplement renvoyer une erreur indiquant à l'utilisateur de la définir.

Oui, la définition de la variable d'environnement après l'initialisation du contexte cuda n'a aucun effet, donc malheureusement, c'est une solution tout ou rien. Lancer une erreur indiquant à l'utilisateur de le définir semble raisonnable.

Actuellement, il ne semble pas possible de vérifier la version CUDA à partir d'un fichier compilé non nvcc, je pense donc devoir l'ajouter à aten/src/ATen/cuda/detail/CUDAHooks.h (la vérification de la version cuDNN fait partie de cette interface) . Si quelqu'un sait mieux, s'il vous plaît faites le moi savoir.

Le commit ci-dessus ajoute l'erreur. Mais je dois trouver quoi faire avec les tests unitaires maintenant. Il y a deux problèmes :

  • Afin de tester que l'erreur est renvoyée dans le bon cas (cuda >= 10.2 et CUBLAS_WORKSPACE_CONFIG n'est pas défini correctement), l'infrastructure de test devrait être capable de modifier automatiquement la variable d'environnement avant d'exécuter un test
  • Pour s'assurer que les tests torch.set_deterministic existants ne cassent pas, nous aurions besoin de définir automatiquement CUBLAS_WORKSPACE_CONFIG correctement. Nous pourrions potentiellement simplement définir cette variable par défaut dans tous les travaux CI qui utilisent cuda >= 10.2.

J'ai découvert que je pouvais définir des variables d'environnement à partir d'un script python, puis recharger le module torche pour qu'il respecte la nouvelle valeur :

>>> 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)

Je ne sais pas si le rechargement de la torche amènera également CUDA à honorer ce changement, mais au moins cela nous donne un moyen de tester unitairement le message d'erreur. Bien que je doive demander, pourrait-il y avoir un problème avec le rechargement du module de torche à l'intérieur d'un test unitaire ?

EDIT : il s'avère que je n'ai pas besoin de recharger la torche pour lui faire voir la variable d'environnement modifiée. De plus, le rechargement après la modification de la variable n'affecte pas l'exécution de CUDA.

Le commit ci-dessus répond à toutes les préoccupations que j'ai mentionnées dans mon commentaire précédent. J'ai ajouté un décorateur pour envelopper tout test d'API qui appelle torch.set_deterministic() , en définissant temporairement CUBLAS_WORKSPACE_CONFIG=:4096:8 uniquement si nécessaire. Il restaure également l'indicateur déterministe et les paramètres CUBLAS_WORKSPACE_CONFIG à ce qu'ils étaient avant l'exécution du test.

J'ai réalisé que la doc de reproductibilité mentionne que le comportement déterministe de CuDNN nécessite :

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

Est-ce que quelqu'un sur ce fil sait ce qu'est exactement benchmark et pourquoi torch.backends.cudnn.deterministic = True en soi n'est pas suffisant ?

Nous pourrions vouloir forcer benchmark à être désactivé si torch.is_deterministic() == True . En d'autres termes, au lieu de passer ctx.benchmarkCuDNN() directement dans at::_convolution() , cela devrait peut-être être ctx.benchmarkCuDNN() && !ctx.deterministic() sur cette ligne : https://github.com/pytorch/pytorch/blob/ master/aten/src/ATen/native/Convolution.cpp#L602

Si nous ne faisons pas ce changement, il semble que les personnes qui utilisent set_deterministic et CuDNN devront le faire :

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

Cela signifie que set_deterministic() seul ne couvrirait pas tout, ce qui est déroutant à mon avis.

cc @ezyang @colesbury @t-vi @ngimel

Lorsqu'il rencontre une nouvelle configuration de convolution, benchmark=True exécute toutes les implémentations cudnn disponibles et sélectionne la plus rapide, mettant en cache l'implémentation qu'elle a choisie, de sorte que tous les appels ultérieurs à la convolution avec les mêmes paramètres l'utiliseront. Ainsi, si deterministic est également défini sur True les résultats seront déterministes tant que ce cache persiste, c'est-à-dire tant que vous êtes dans le même processus. S'il existe des implémentations avec une exécution proche, la prochaine fois que vous démarrez le processus et exécutez à nouveau l'analyse comparative, une autre implémentation peut gagner et les résultats (bien que toujours déterministes au sens décrit ci-dessus) seront différents de l'exécution précédente. Ainsi, pour garantir le déterminisme entre les exécutions, vous devez désactiver l'analyse comparative.

Je vois. Donc peut-être que seul le déterminisme en cours, pas le déterminisme inter-processus, compte pour certaines applications, donc les gens pourraient trouver utile de pouvoir toujours utiliser l'analyse comparative s'ils définissent torch.set_deterministic(True) . Dans ce cas, je ne devrais pas changer le comportement actuel. Tant que je mets à jour la documentation pour que cela soit clair, je n'y vois pas de problème.

J'ai créé une page wiki pour aider les contributeurs de PyTorch à ajouter un support pour torch.set_deterministic() : https://github.com/pytorch/pytorch/wiki/How-to-support-%60torch.set_deterministic ()%60-in- Opérateurs PyTorch

Toute amélioration est la bienvenue.

De plus, je ne savais pas si la section "Fonctions actuellement non prises en charge" devrait être dans ce wiki ou si ce serait mieux en tant que nouveau problème github (la page wiki pourrait y être liée). Quelqu'un a-t-il une préférence ?

Bonjour, je veux parler du plan à venir pour torch.deterministic . Il y a quelques questions de haut niveau auxquelles nous devons répondre :

  1. Quelle est la sémantique de torch.deterministic ? Qu'attend l'utilisateur ? Le meilleur effort est-il réellement utile pour un utilisateur ? Si ce n'est pas utile, est - il préférable de définir torch.deterministic en termes de ce que les opérations qu'il contrôle?
  2. Maintenant que nous avons le drapeau torch.deterministic , est-il logique d'éliminer entièrement l'argument de mot-clé deterministic= de l'API publique ( bmm , je vous regarde).
  3. Quel est le jeu final de ce travail ? Sur quelle partie (@kurtamohler) allez-vous travailler, par rapport à la communauté générique, et quand nous arriverons à la fin de votre séjour ici, à quoi ressemble un état raisonnable ?

À partir de (1), la documentation actuelle de torch.deterministic dit :

     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.

Bien que cela puisse être vrai pour un état final éventuel, cela représente de manière inexacte la situation actuelle, où de nombreuses opérations n'ont pas été auditées et pour un modèle donné, nous ne savons pas si torch.deterministic fera réellement ce qu'il dit sur l'étain et rendez votre modèle déterministe/générez une erreur lorsque vous appuyez sur nondet. Donc, fondamentalement, notre implémentation est boguée par rapport à cette sémantique, et continuera de l'être dans un avenir prévisible. Ce n'est pas un bon état dans lequel être.

Nous pourrions modifier la documentation de torch.deterministic pour améliorer cela. Quelques changements possibles :

  • torch.deterministic est le meilleur effort , mais veuillez signaler les bogues si vous voyez qu'il n'attrape pas un certain non-déterminisme
  • torch.deterministic bascule le comportement de ces opérateurs (et donne ensuite une liste exhaustive des opérateurs qu'il bascule)

Le deuxième point mène à (2) : si torch.deterministic existe désormais comme moyen de basculer le déterminisme, il est beaucoup moins important de prendre en charge le déterminisme directement dans l'API utilisateur. Nous n'aurions donc probablement pas dû ajouter l'argument deterministic à bmm. Nous pourrions envisager d'exposer une fonction interne si vous souhaitez basculer quelque chose directement, mais deterministic ne devrait pas être disponible directement sur la fonction elle-même.

Qu'en penses-tu? Je pense que changer les documents est probablement le moyen le plus simple de s'engager sur une voie durable. Il y a d'autres détails, comme comment remplir la liste exhaustive, mais cette sémantique a probablement plus de sens que la sémantique "idéale" qui ne sera pas vraie.

cc @gchanan @mruberry

@ zou3519 a également croisé le Q sur https://github.com/pytorch/pytorch/pull/38683#issuecomment -662590937

Je suis content que vous ayez soulevé ces questions @ezyang , @zou3519 et @mruberry. J'accepte que la documentation que j'ai écrite est une fausse représentation de l'état actuel.

J'aime l'idée de lister de manière exhaustive toutes les fonctions affectées par torch.set_deterministic() , afin de ne pas mentir à l'utilisateur. Merci d'avoir ajouté cela à la 1.6.0, @zou3519.

Je suis d'accord pour dire que nous ne devrions pas proposer le paramètre deterministic comme arguments de fonction directs.

En ce qui concerne le jeu final, je suis heureux de continuer à travailler dessus aussi longtemps que nécessaire, mais il devrait être mis en place de manière à ce que chacun puisse rapidement apprendre à aider.

À long terme, je pense que fournir une liste exhaustive des fonctions affectées est une décision valable, mais je ne pense pas que cette stratégie à elle seule maximiserait l'utilité du drapeau déterministe. Nous pouvons catégoriser les fonctions (dans un environnement spécifique) comme ceci :

  1. Déterministe
  2. Non déterministe par défaut, mais prend en charge l'indicateur déterministe (erreur ou implémentation alternative)
  3. Non déterministe et ne prend pas en charge l'indicateur déterministe

Bien sûr le cas idéal est d'éliminer complètement la catégorie 3, et alors la liste des fonctions de la catégorie 2 serait suffisante. Cependant, les fonctions de catégorie 3 existeront toujours pendant une période de temps significative (ou peut-être pour toujours, si tous les contributeurs ne sont pas conscients de la question du déterminisme, ou si un commit supprime accidentellement le déterminisme d'une fonction, etc.). Ainsi, même si nous avons une liste exhaustive de toutes les fonctions de catégorie 2, l'utilisateur n'a aucun moyen simple de savoir si une fonction qui n'apparaît pas sur la liste est déterministe ou non (cela peut être de catégorie 1 ou 3). Par exemple, torch.add n'apparaît pas dans la liste, alors comment l'utilisateur sait-il que c'est déterministe ?

Peut-être pourrions-nous également envisager de maintenir une liste de fonctions de catégorie 3. Mais maintenir manuellement ces listes serait très difficile pour de nombreuses raisons, alors je me demande si nous pourrions l'automatiser quelque peu. Nous pourrions potentiellement mettre en place un travail CI qui exécute des tests de déterminisme sur toutes les fonctions. Il n'est pas possible de prouver à 100% par induction qu'une fonction est déterministe, et une fonction non déterministe peut parfois donner le même résultat plusieurs fois si nous n'avons pas de chance. Mais plus nous exécutons ces tests souvent, plus nous pouvons devenir confiants quant à la catégorie à laquelle appartient chaque fonction.

Il s'agit également de savoir comment transmettre le plus efficacement possible à l'utilisateur tout ce que nous savons et ne savons pas sur chaque fonction et chaque plate-forme. Peut-être pourrions-nous faire un tableau de toutes les fonctions des catégories 2 et 3 sur chaque plate-forme. Ce serait bien si les tests de déterminisme pouvaient vérifier automatiquement que cette table est correcte.

Juste un remue-méninges, peut-être que ces idées sont plus difficiles qu'elles ne valent. Un plan plus pragmatique pourrait être nettement plus durable, même s'il est moins idéal.

Le torch.add déterministe ?

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')

Nous devrions probablement documenter que les tenseurs superposés violent le contrat de déterminisme que nous recherchons.

Énumérer les opérations affectées par un indicateur de « déterminisme » semble bien. En prenant un peu de recul, cependant, il semble que nous parlions vraiment de deux choses :

  • Demande de versions déterministes des opérations, si disponibles ( use_deterministic ?)
  • Avertissement si une opération est non déterministe

Un drapeau pour la première chose semble simple. La seconde, cependant, est un peu plus délicate. Je crains qu'il soit difficile de dire si les opérations des bibliothèques mathématiques comme oneDNN, cuDNN et MAGMA sont déterministes, en particulier entre les versions et le matériel. Avez-vous une idée de la meilleure façon de résoudre ce problème, @kurtamohler ? Peut-être pourrions-nous avertir de toutes les opérations non déterministes natives et également avertir lorsque des appels à la bibliothèque mathématique ont été effectués ? Un avertissement par processus ne devrait pas être si intrusif.

Cette approche des avertissements nécessiterait de revoir de nombreux algorithmes et sites d'appels avant d'être mis en ligne, mais il n'est pas nécessaire de bloquer le drapeau pour sélectionner des algorithmes déterministes s'ils sont disponibles.

(Une troisième chose en cours de discussion est la meilleure façon de présenter la sélection d'algo déterministe (via un indicateur global ou en tant que kwargs sur les fonctions), mais je pense que nous pouvons retarder cette discussion jusqu'à ce que nous déterminions un plan pour le(s) indicateur(s) ?)

Je pense que nous ne devrions pas laisser le parfait être l'ennemi du bien ici. Je ne sais pas quand il était sûr à 100% d'utiliser des tenseurs à chevauchement automatique avec PyTorch, et j'ai l'impression que ce n'est pas que les gens ordinaires les utilisent.

Mon impression sur les forums est que la plupart des gens sont surpris qu'ils exécutent quelque chose deux fois et obtiennent des gradients différents, le plus souvent à cause de l'une des fonctions natives de PyTorch utilisant atomicAdd.
Si nous recevons des avertissements pour cela, nous avons couvert la plupart des cas sur lesquels les gens se posent des questions. Quelque chose qui semble être la moitié de celui-ci provient en fait d'une mise à l'échelle vers l'arrière.

Je pense que nous devrions clairement indiquer que c'est le meilleur effort en ce qui concerne les bibliothèques externes et que nous ajoutons des avertissements au fur et à mesure que nous apprenons à connaître les problèmes, mais mon impression est que nos noyaux natifs sont en fait ce qui compte le plus.

Je ne sais pas quand il était sûr à 100% d'utiliser des tenseurs à chevauchement automatique avec PyTorch, et j'ai l'impression que ce n'est pas que les gens ordinaires les utilisent.

Oui, et tous les programmes qui le font pourraient raisonnablement être classés comme erronés. Je voulais juste dire que nous devrions faire attention à documenter le contrat que nous concluons pour ces drapeaux.

Je pense que nous devrions clairement indiquer qu'il s'agit du meilleur effort en ce qui concerne les bibliothèques externes et que nous ajoutons des avertissements au fur et à mesure que nous apprenons à connaître les problèmes ...

Le doc pourrait dire quelque chose comme « appels de bibliothèques mathématiques connus pour être non déterministes... » ?

Je suis d'accord avec @t-vi (et j'aime beaucoup l'observation selon laquelle la moitié du non-déterminisme signalé est rétrogradé). En particulier, je pense qu'un état où nous avons des fonctions partiellement documentées qui sont connues pour être non déterministes (ou même partiellement documentées certaines fonctions comme étant déterministes) est strictement meilleur qu'un état où nous ne donnons aucune indication du tout - l'essentiel est de ne pas prétendre soutenir des choses que nous ne soutenons pas ! Je suis d'accord que c'est une activité utile de réfléchir à la façon dont on pourrait procéder pour tester le déterminisme, mais je pense que c'est une activité orthogonale pour signaler des API qui sont évidemment non déterministes.

Étant donné que de nombreuses idées circulent, permettez-moi d'apporter mes réflexions spécifiques sur certaines d'entre elles :

  1. « Peut-être pourrions-nous également envisager de maintenir une liste de fonctions de catégorie 3 ». Cela semble beaucoup de travail. Je pense que cela ne vaut probablement la peine que pour les fonctions pour lesquelles nous avons explicitement fait quelques adaptations pour le déterminisme (très probablement, les fonctions qui prennent en charge le drapeau déterministe)
  2. « Nous pourrions potentiellement mettre en place un travail CI qui exécute des tests de déterminisme sur toutes les fonctions. » Je pense que quelque chose comme ça devrait être fait avec beaucoup de soin, car de par sa nature même, il teste quelque chose qui n'est pas déterministe, et cela signifie que le test de déterminisme lui-même est "floconneux" (il réussira parfois et échouera d'autres) . Nos outils de reporting CI ne gèrent pas très bien de telles situations.
  3. "La seconde, cependant, est un peu plus délicate. Je crains qu'il soit difficile de dire si les opérations des bibliothèques mathématiques comme oneDNN, cuDNN et MAGMA sont déterministes, en particulier entre les versions et le matériel." Nous devrions faire de notre mieux pour cela. Dans de nombreux cas, la bibliothèque mathématique spécifie explicitement dans sa documentation qu'ils sont déterministes ou non, et nous devrions simplement rapporter fidèlement ce que disent les docs
  4. « Peut-être pourrions-nous avertir de toutes les opérations non déterministes natives et également avertir lorsque des appels à la bibliothèque mathématique ont été effectués ? » Je ne pense pas que nous devrions faire cela. Lorsque nous mettons en garde contre le non-déterminisme, ce devrait être parce que le non-déterminisme EST en train de se produire, pas qu'il PEUT se produire. Si vous avertissez trop, les gens commenceront à ignorer les avertissements.

Je ne pense pas que nous devrions nous inquiéter du déterminisme de version/matériel croisé -- bonne chance avec ça.

Lorsque nous mettons en garde contre le non-déterminisme, ce devrait être parce que le non-déterminisme EST en train de se produire, pas qu'il PEUT se produire. Si vous avertissez trop, les gens commenceront à ignorer les avertissements.

ça a l'air délicat. Par exemple, que se passe-t-il si j'exécute une opération et que l'implémentation de PyTorch est déterministe, mais qu'une extension a remplacé quelque chose (via une clé de répartition, une fonction torche ou autre) et maintenant je ne sais pas. Si c'est effectivement la source de mon non-déterminisme, cela semble être une déception de ne pas être prévenu.

Si c'est effectivement la source de mon non-déterminisme, cela semble être une déception de ne pas être prévenu.

Bien sûr, mais l'utilisateur pourrait également ne pas nous impliquer dans ses manigances non déterministes, et bien sûr, vous ne vous attendriez pas à être averti à ce moment-là ;)

Je pense que nous pouvons clore ce problème maintenant puisque l'API flag existe et est bien documentée.

@kurtamohler Super travail. Merci.

Cela signifie-t-il que nous pourrions utiliser torch.manual_seed(111) pour que tout soit déterministe, y compris l'opération interpolation ?

Non. Jetez un œil à la note Reproductibilité / Aléatoire .
Jusqu'à présent, nous avons une infrastructure, marqué les sources connues sur le non-déterminisme et une documentation grandement améliorée afin que vous puissiez savoir ce qui se passe.
Si vous frappez des opérations non déterministes, vous n'avez toujours pas de chance, mais il est maintenant plus raisonnable de travailler dessus.

L'interpolation en particulier semble quelque chose qui pourrait être rendu déterministe en écrivant un noyau pas si compliqué que ça pour l'arrière.

@t-vi Salut, Maintenant que pytorch 1.7 est sorti, le noyau d'interpolation arrière a-t-il été mis à jour ?

Ainsi, les noyaux de suréchantillonnage CUDA et inversement se trouvent dans aten/src/ATen/native/cuda/UpSample* . Un grep suggère que linéaire, bilinéaire, cubique ont un retour non déterministe (ils ont un marqueur d'avertissement), mais le plus proche n'en a pas.
@kurtamohler serait la bien meilleure personne à qui demander, cependant.

Cette page vous a été utile?
0 / 5 - 0 notes