Deberíamos agregar una variable global para forzar a PyTorch a usar algoritmos deterministas bit a bit. Soumith sugiere agregar la bandera a un subpaquete torch.experimental
porque no estamos seguros de algunos de los detalles.
El determinismo bit a bit entre ejecuciones a veces es útil para la depuración. Sin embargo, es difícil escribir algoritmos deterministas eficientes para algunas operaciones.
Cuando torch.experimental.deterministic
es False
(el valor predeterminado), PyTorch debe usar el algoritmo más rápido disponible para una operación determinada. Cuando torch.experimental.deterministic
es True
, PyTorch solo debe usar algoritmos deterministas. PyTorch debería emitir una advertencia si no tenemos un algoritmo determinista disponible para una operación determinada y torch.experimental.deterministic
es True
.
Ya tenemos una bandera torch.backends.cudnn.deterministic
para controlar las opciones del algoritmo cuDNN. Deberíamos mantener esta bandera por ahora y restringir cuDNN a algos deterministas si torch.backends.cudnn.deterministic
o torch.experimental.deterministic
es True.
Solo apuntamos al determinismo bit a bit entre ejecuciones en máquinas con la misma arquitectura y configuración. Por ejemplo, incluso cuando torch.experimental.deterministic
es Verdadero, no apuntamos al determinismo bit a bit cuando cualquiera de las siguientes variaciones:
Sugiero agregar esta función en dos pasos. El primer paso es agregar la bandera torch.backends.cudnn.deterministic
y agregar advertencias a cualquier operación no determinista. El segundo paso es agregar implementaciones deterministas para las operaciones no deterministas.
Hay una lista parcial de operaciones no deterministas en los documentos de PyTorch .
¿Cómo debería interactuar torch.experimental.deterministic
con la semilla RNG? ¿Debería establecer una semilla predeterminada si no se ha establecido una semilla manual? ¿Debería emitir una advertencia si no se ha establecido una semilla manual?
cc @ezyang @gchanan @ zou3519
Este es un pulgar hacia arriba de mi parte. El problema será principalmente cómo implementar esto en todas partes en la base de código; nada peor es afirmar que somos deterministas, pero luego, en secreto, no lo es :)
Estoy totalmente de acuerdo y mi enfoque sería marcar operaciones y errores cuando determinista está activado y sabemos que no lo están.
Creo que cometer errores en operaciones no deterministas es demasiado severo. La advertencia parece una experiencia más fluida
Creo que el valor predeterminado debería ser lanzar, pero supongo que podríamos admitir una propiedad de varios valores allí (no determinista está bien, advertir, lanzar).
Debo admitir que realmente no veo el caso de uso de una advertencia. Cuando a las personas les importa lo suficientemente determinista como para encenderlo, probablemente esperarían el error. Siempre puede apagarlo para ciertas llamadas para decir que está de acuerdo con cualquier no determinismo que haya allí.
Error, advertencia, documentación adecuada ...
Este último es imprescindible.
¿Advertencia o error? Iré con un error.
lanzar parece genial. Estoy de acuerdo con Adam en que dar la opción de advertir en lugar de lanzar parece razonable.
Gracias por opinar. Al final, el esfuerzo principal para la bandera ternaria es la bandera en sí, y eso no es difícil.
Agregaré una bandera a Context.hy esparciré (a través de una función de utilidad) AT_ERROR y AT_CHECK.
Hola,
¿Alguna novedad sobre esta bandera?
El determinismo es crucial.
Desde mi experiencia, la versión actual permite el determinismo sobre una gpu, hasta una precisión 1e-16
, usando semillas fijas. Tenga en cuenta que la diferencia infinitesimal puede amplificarse y divergir los resultados.
Por favor, considere el caso de multigpu también (al menos para un K
gpus fijo, el comportamiento debe ser determinista. Puedo lograr algún tipo de determinismo que se rompe de vez en cuando por una razón que no lo entiendo por ahora (usando la compilación nocturna 1.2.0.dev20190616
).). Estoy luchando con eso en este momento ( 1 , 2 ).
¡Gracias!
@ t-vi, ¿estás trabajando activamente en esto?
No quiero impedirte que lo hagas.
@ t-vi Lo siento si no estaba claro, no estoy pensando en trabajar en esto :). Solo estaba tratando de entender si alguien lo estaba haciendo activamente.
Después de casi un año, el problema de la interpolación no determinista aún no está resuelto.
Espero que la comunidad agregue esta característica :)
Quizás una interpolación determinista sería de gran ayuda para los usuarios.
~ Realmente no lo anuncié todavía, pero dado que parece haber más interés de los usuarios que los recursos de desarrollador asignados, tengo esto en la lista como un proyecto sobre el que puedes votar en mi página de patrocinio de github cuando configuro esto.
Estoy bastante seguro de que podríamos hacer un buen progreso para fin de año y la interpolación ciertamente es una de las cosas que tengo un plan de cómo arreglar (similar al pseudocódigo para pliegue que estoy en algún lugar de los problemas) pero simplemente no está en lo alto de mi propia lista de prioridades. ~
Resultó no ser interesante.
una interpolación determinista será de gran ayuda. Enlace
Prioridad de golpe, especialmente para CUDA, según los comentarios de los usuarios
Me alegro de que lo estén arreglando, ¡gracias!
@ t-vi para ser justos, no creo que la "prioridad de golpe" sea equivalente a "se está arreglando" :).
Esperando las soluciones!
colesbury mencionó que una razón decisiva para los algoritmos deterministas no es porque el determinismo sea en realidad el problema, sino que puede descartarlo cuando enciende esto;)
¿Cómo debería interactuar
torch.experimental.deterministic
con la semilla RNG? ¿Debería establecer una semilla predeterminada si no se ha establecido una semilla manual? ¿Debería emitir una advertencia si no se ha establecido una semilla manual?
Sugeriría no establecer una semilla si el usuario no ha establecido ninguna. Por un lado, porque combina dos interfaces que no son necesarias (creo que los usuarios que se preocupan por el determinismo entenderán muy bien los RNG). Más importante aún, esto es muy difícil de hacer de manera confiable; uno puede usar un RNG en aplicaciones multiproceso / subproceso, tener otras subclases torch.Generator
, usar numpy.random
también, etc.
No estoy seguro de una advertencia, solo si hay un lugar sensato para configurarla (por ejemplo, ¿está forzando a sembrar antes de determinism=True
lugar de en el mismo módulo / función donde se usa un RNG?).
Solo tengo curiosidad de que cuando configuro torch.backends.cudnn.deterministic=True
, el operador de interpolación aún no puede ser determinista. ¿La interpolación de pytorch no usa cudnn?
Puede que no. Puede nvprof su ejecución de interpolación para verificar con certeza.
Me pregunto si deberíamos seguir proporcionando los argumentos deterministic
en las llamadas a funciones una vez que se implemente torch.experimental.deterministic
. Quizás deberíamos hacerlo, porque un usuario podría preferir el determinismo para algunas operaciones y la velocidad para otras operaciones.
Si mantenemos los argumentos, ¿qué sucede si torch.experimental.deterministic
y la bandera deterministic
una función se oponen entre sí? ¿Debería torch.experimental.deterministic = True
significar "usar determinismo en todos los casos sin importar qué", o debería significar "usar determinismo como valor predeterminado, pero si el argumento deterministic
se especifica en una llamada de función, entonces use esa configuración para esa llamada de función específica ". En otras palabras, ¿cómo se debe manejar el siguiente código? ¿Alguien sabe cómo actúa la bandera torch.backends.cudnn.deterministic
en una situación similar?
torch.experimental.deterministic = True
torch.some_operation(deterministic=False)
@kurtamohler Buena pregunta. Creo que la solución más fácil es hacerlo bool? deterministic=None
, y luego interpretar que None
significa "respetar torch.experimental.deterministic
" y, de lo contrario, usar exactamente lo que el usuario solicitó.
Tenemos una situación similar con la convolución, pero la forma en que se hizo allí fue que hay un convolution
sin benchmark
argumento, y luego un _convolution
con un argumento explícito punto de referencia.
Creo que cualquiera de estas soluciones sería aceptable; sin embargo, el enfoque de convolución tiene el beneficio adicional de no filtrar el indicador interno deterministic
a la API visible para el usuario (a menos que utilicen una API interna).
¿Cuál es la razón fundamental para "Quiero ser determinista en todas partes, pero no en este operador en particular"? ¿Se supone que este es realmente un caso de uso lo suficientemente común como para justificar la adición de una entrada adicional a muchos de nuestros operadores (y la mayoría de los complejos)? En mi opinión, sería mejor proporcionar administradores de contexto para alternar el determinismo.
@apaszke , sí, creo que tienes razón en que sería mejor usar administradores de contexto para alternar el determinismo. No diría que deberíamos agregar el argumento deterministic
a ningún operador, pero algunos operadores ya lo tienen. ¿Sería mejor eliminarlos todos y romper el BC, o sería mejor mantenerlos cerca y permitirles anular torch.experimental.deterministic
?
Yo diría que deberíamos eliminarlo o hacerlo privado al menos (es decir, prefijo de subrayado o algo).
Me pregunto si la característica determinista para la función de interpolación está cerrada y no se implementará.
No, estamos dispuestos a versiones deterministas de TODAS las funciones en PyTorch
@ezyang ¿ Qué versión de Pytorch tiene la función determinista F.interpolate? ¿está comenzando desde pytorch 1.6? ¿O está disponible en la última versión estable (1.5)? ¿O tengo que descargar e instalar Pytorch desde la fuente?
Estaría feliz de empezar a trabajar en esto
La confirmación anterior solo agrega la bandera, todavía no afecta ninguna operación. Agradecería que alguien se tomara unos minutos para mirarlo y decirme si hice algo incorrecto o si algo podría mejorarse hasta ahora. Basé esto en cómo se implementa torch.backends.cudnn.deterministic
.
Esto se ve bien, pero siento que el nombre interno no debería incluir experimental (ya que, aparentemente, algún día querrás que no sea experimental, ¡y eso no debería implicar tener que cambiar el nombre de todos los bits de implementación!)
@ezyang , sí, eso tiene sentido,
Agregué un torch.experimental.deterministic_error_level
, similar a lo que hizo @ t-vi en su trabajo anterior sobre este tema. deterministic_error_level
controla el comportamiento de error / advertencia si deterministic == True
y una función determinada no tiene una implementación determinista. Se puede configurar en 2 (error), 1 (advertir) o 0 (silencioso).
Si el usuario lo establece en cualquier otro valor, quiero lanzar una excepción de tiempo de ejecución de Python capturable. Por lo general, usaría TORCH_CHECK()
para ese tipo de comportamiento, pero en este caso, la excepción no se puede detectar y no estoy seguro de por qué. Aquí está el enlace TORCH_CHECK()
call:
Esto es lo que sucede cuando esa verificación falla:
>>> 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 alguien sabe cómo puedo solucionarlo, hágamelo saber.
@kurtamohler ¿ THPModule_setDeterministicErrorLevel
faltan macros HANDLE_TH_ERRORS / END_ HANDLE_TH_ERRORS? Son necesarios para detectar la excepción de C ++ y traducirla en un retorno de error de Python.
¡Ah, eso fue todo, gracias @colesbury!
Estoy empezando a agregar la alerta no determinista a todas las personas que llaman de atomicAdd
. Noté que algunas personas que llaman solo usan atomicAdd
en ciertos casos. Por ejemplo, adaptive_avg_pool3d_backward
solo se usa si (isizeW%osizeW != 0) || (isizeH%osizeH != 0) || (isizeT%osizeT != 0)
es verdadero. ¿Debo alertar solo en estos casos e intentar transmitirlos en el mensaje de error, o estaría bien solo alertar cada vez que se invocan estas funciones si atomicAdd
termina siendo utilizado o no?
Probablemente sea más fácil de implementar y más fácil de entender si alerta incondicionalmente.
@ngimel , he estado pensando en cómo usar CUBLAS_WORKSPACE_CONFIG
para asegurar el uso de la transmisión determinista, y creo que hay dos enfoques principales que deben tenerse en cuenta.
Si alguien está usando una de las versiones de CUDA afectadas (10.2 o superior en este momento) y se llama a torch.set_deterministic(True)
, use std::getenv
para asegurarse de que CUBLAS_WORKSPACE_CONFIG
sea :16:8
o :4096:8
. Si no es así, haga (1) o (2):
Lanza un error que le dice al usuario que configure la variable de manera apropiada.
Configure automáticamente la variable con putenv
( _putenv
en Windows). Sin embargo, hay algunas decisiones de diseño adicionales asociadas con esto. ¿Deberíamos elegir :16:8
(menor rendimiento, pero menos uso de memoria) o :4096:8
(mayor rendimiento, pero más uso de memoria)? Además, si el usuario establece la variable en algún otro valor no determinista, tendríamos que realizar un seguimiento del valor original y restaurarlo si se llama torch.set_deterministic(False)
, o podríamos arrojar un error diciéndole al usuario que necesitan desarmar la variable o algún otro esquema.
Además, no sé si establecer la variable mientras la aplicación ya se está ejecutando realmente tendrá algún efecto, por lo que no sé con certeza si la opción (2) es siquiera posible. Es posible que la variable solo se verifique una vez, cuando se inicie el tiempo de ejecución de CUDA o cuando se cree un identificador de cuBLAS. No pude encontrar información sobre esto, por lo que probablemente tendría que averiguarlo experimentalmente (tendré que usar un reproductor de uso de flujo no determinista para escribir una prueba de cualquier manera, así que investigaré esto) . También busqué una llamada a la API, en lugar de usar la variable de entorno, pero CUDA no parece ofrecer una.
¿Tiene una opinión firme sobre qué opción sería mejor? La opción (2) probablemente sería más fácil de usar, pero posiblemente menos transparente que la opción (1).
No sé si establecer la variable mientras la aplicación ya se está ejecutando realmente tendrá algún efecto
Para continuar con esta pregunta, la configuración de la variable de entorno dentro de un script de pytorch no parece afectar el determinismo de la secuencia CUDA. Modifiqué el script de https://github.com/pytorch/pytorch/issues/39849 para ejecutarlo varias veces y comparar las estadísticas de entrenamiento para verificar el comportamiento no determinista. Intenta establecer CUBLAS_WORKSPACE_CONFIG=:4096:8
para garantizar el uso de la transmisión determinista: https://github.com/kurtamohler/pytorch-perf-test-scripts/blob/master/nondeterministic_alert/cuda_stream_nondeterminism.py
Ejecutarlo muestra que no obtenemos un comportamiento determinista al configurar la variable dentro del script:
$ python cuda_stream_nondeterminism.py
Before setting var: not deterministic
After setting var: not deterministic
After restoring old var: not deterministic
Pero ejecutarlo con la variable de entorno establecida fuera del script lo hace determinista:
$ 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
Tenga en cuenta que imprime "posiblemente determinista" porque solo ejecuto la función de entrenamiento 5 veces, y es posible tener suerte incluso si el comportamiento no es realmente determinista.
Tal vez si pudiera reinicializar el flujo cuda, eso lo obligaría a respetar la variable CUBLAS_WORKSPACE_CONFIG
modificada. Me gustaría probar eso, pero no sé cómo o si es posible hacerlo en tiempo de ejecución. Si alguien lo sabe, hágamelo saber.
Descubrí que puedo crear y usar una nueva transmisión con:
with torch.cuda.stream(torch.cuda.Stream()):
Pero la nueva transmisión no respeta la configuración de la variable de entorno modificada. También encontré torch.cuda.init()
, pero desafortunadamente, eso no es una operación si cuda ya se ha inicializado.
Entonces, a menos que podamos pensar en algo más para probar, parece que probablemente no podamos cambiar la configuración del espacio de trabajo automáticamente, por lo que es posible que tengamos que lanzar un error que le indique al usuario que lo configure.
Sí, configurar la variable de entorno después de que se haya inicializado el contexto cuda no tiene ningún efecto, por lo que, desafortunadamente, es una solución de todo o nada. Lanzar un error que le dice al usuario que lo configure suena razonable.
Actualmente, no parece que sea posible verificar la versión CUDA desde un archivo compilado que no sea nvcc, así que creo que tendré que agregar eso a aten/src/ATen/cuda/detail/CUDAHooks.h
(verificar la versión cuDNN es parte de esa interfaz) . Si alguien lo sabe mejor, hágamelo saber.
La confirmación anterior agrega el error. Pero necesito averiguar qué hacer con las pruebas unitarias ahora. Hay dos problemas:
CUBLAS_WORKSPACE_CONFIG
no está configurado correctamente), la infraestructura de prueba debería poder cambiar automáticamente la variable de entorno antes de ejecutar una pruebatorch.set_deterministic
no se rompan, necesitaríamos configurar automáticamente CUBLAS_WORKSPACE_CONFIG
correctamente. Potencialmente, podríamos establecer esta variable de forma predeterminada en todos los trabajos de CI que usan cuda> = 10.2.Descubrí que puedo establecer variables de entorno desde un script de Python, luego recargar el módulo de antorcha para que respete el nuevo valor:
>>> 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)
No sé si recargar la antorcha también hará que CUDA respete este cambio, pero al menos esto nos da una forma de realizar una prueba unitaria para el mensaje de error. Aunque tengo que preguntar, ¿podría haber algún problema con la recarga del módulo de la antorcha dentro de una prueba unitaria?
EDITAR: Resulta que no necesito recargar la antorcha para que vea la variable de entorno cambiada. Además, la recarga después de cambiar la variable no afecta el tiempo de ejecución de CUDA.
El compromiso anterior aborda todas las preocupaciones que mencioné en mi comentario anterior. Agregué un decorador para envolver cualquier prueba de API que llame a torch.set_deterministic()
, configurando temporalmente CUBLAS_WORKSPACE_CONFIG=:4096:8
solo si es necesario. También restaura el indicador determinista y la configuración de CUBLAS_WORKSPACE_CONFIG a lo que eran antes de que se ejecutara la prueba.
Me di cuenta de que el documento de reproducibilidad menciona que el comportamiento determinista de CuDNN requiere:
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
¿Alguien en este hilo sabe qué es exactamente benchmark
y por qué torch.backends.cudnn.deterministic = True
por sí solo no es suficiente?
Podríamos querer forzar que benchmark
se apague si torch.is_deterministic() == True
. En otras palabras, en lugar de pasar ctx.benchmarkCuDNN()
directamente a at::_convolution()
, tal vez debería ser ctx.benchmarkCuDNN() && !ctx.deterministic()
en esta línea: https://github.com/pytorch/pytorch/blob/ master / aten / src / ATen / native / Convolution.cpp # L602
Si no hacemos este cambio, parece que las personas que usan set_deterministic
y CuDNN tendrán que hacer esto:
torch.set_deterministic(True)
torch.backends.cudnn.benchmark = False
Lo que significa que set_deterministic()
solo no cubriría todo, lo cual es confuso en mi opinión.
cc @ezyang @colesbury @ t-vi @ngimel
Al encontrar una nueva configuración de convolución, benchmark=True
ejecuta todas las implementaciones de cudnn disponibles y elige una más rápida, almacenando en caché la implementación que eligió, por lo que todas las llamadas posteriores a la convolución con los mismos parámetros la usarán. Por lo tanto, si deterministic
también se establece en True
los resultados serán deterministas siempre que este caché persista, es decir, mientras esté en el mismo proceso. Si hay implementaciones con un tiempo de ejecución cercano, la próxima vez que inicie el proceso y ejecute la evaluación comparativa nuevamente, otra implementación puede ganar y los resultados (aunque aún deterministas en el sentido descrito anteriormente) serán diferentes de la ejecución anterior. Por lo tanto, para garantizar el determinismo entre ejecuciones, debe desactivar la evaluación comparativa.
Veo. Entonces, tal vez solo el determinismo en proceso, no el determinismo entre procesos, sea importante para algunas aplicaciones, por lo que las personas podrían encontrar útil seguir utilizando la evaluación comparativa si establecen torch.set_deterministic(True)
. En ese caso, no debería cambiar el comportamiento actual. Siempre que actualice los documentos para dejarlo en claro, no veo ningún problema con eso.
Hice una página wiki para ayudar a los colaboradores de PyTorch a agregar soporte para torch.set_deterministic()
: https://github.com/pytorch/pytorch/wiki/How-to-support-%60torch.set_deterministic ()% 60-in Operadores de PyTorch
Cualquier mejora es bienvenida.
Además, no estaba seguro de si la sección "Funciones actualmente no admitidas" debería estar en este wiki o si sería mejor como un nuevo problema de github (la página del wiki podría enlazarlo). ¿Alguien tiene alguna preferencia?
Hola, quiero hablar sobre el plan en el futuro por torch.deterministic
. Hay algunas preguntas de alto nivel que debemos responder:
torch.deterministic
? ¿Qué espera el usuario? ¿Es el mejor esfuerzo realmente útil para un usuario? Si no es útil, ¿es mejor definir torch.deterministic
en términos de qué operaciones controla?torch.deterministic
, ¿tiene sentido eliminar el argumento de palabra clave deterministic=
completo de la API pública ( bmm
, te estoy mirando)?Comenzando con (1), la documentación actual para torch.deterministic dice:
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.
Si bien esto puede ser cierto para un eventual estado final, esto representa de manera inexacta la situación actual, donde muchas operaciones no han sido auditadas y para cualquier modelo dado, no sabemos si torch.deterministic
realmente hará lo que hace. dice en la lata y hace que su modelo sea determinista / genere un error cuando presione nondet. Entonces, básicamente, nuestra implementación tiene errores con respecto a esta semántica, y seguirá teniendo errores en el futuro previsible. Este no es un gran estado para estar.
Podríamos cambiar la documentación de torch.deterministic para mejorar esto. Algunos posibles cambios:
El segundo punto conduce a (2): si torch.deterministic ahora existe como una forma de alternar el determinismo, es mucho menos importante admitir el determinismo directamente en la API del usuario. Así que probablemente no deberíamos haber agregado el argumento deterministic
a bmm. Podríamos considerar exponer una función interna si desea alternar algo directamente, pero deterministic
no debería estar disponible directamente en la función en sí.
¿Qué piensas? Creo que cambiar los documentos es probablemente la forma más fácil de emprender un camino sostenible. Hay algunos otros detalles, como cómo completar la lista exhaustiva, pero esta semántica probablemente tenga más sentido que la semántica "ideal" que en realidad no va a ser cierta.
cc @gchanan @mruberry
@ zou3519 también se cruzó con la Q en https://github.com/pytorch/pytorch/pull/38683#issuecomment -662590937
Me alegra que hayas mencionado estas preguntas a @ezyang , @ zou3519 y @mruberry. Acepto que la documentación que escribí es una representación falsa del estado actual.
Me gusta la idea de enumerar exhaustivamente todas las funciones a las que afecta torch.set_deterministic()
, para no mentirle al usuario. Gracias por agregar eso a 1.6.0, @ zou3519.
Estoy de acuerdo en que no deberíamos ofrecer la configuración deterministic
como argumentos de función directa.
En cuanto al juego final, estoy feliz de seguir trabajando en esto el tiempo que sea necesario, pero debería configurarse para que cualquiera pueda aprender rápidamente cómo ayudar.
A largo plazo, creo que proporcionar una lista exhaustiva de las funciones afectadas es una decisión válida, pero no creo que esa estrategia por sí sola maximice la utilidad de la bandera determinista. Podemos categorizar funciones (en un entorno específico) de esta manera:
Por supuesto, el caso ideal es eliminar completamente la categoría 3, y entonces la lista de funciones de la categoría 2 sería suficiente. Sin embargo, las funciones de categoría 3 seguirán existiendo durante un período de tiempo significativo (o quizás para siempre, si no todos los contribuyentes son conscientes de la cuestión del determinismo, o un compromiso elimina accidentalmente el determinismo para una función, etc.). Entonces, incluso si tenemos una lista exhaustiva de todas las funciones de categoría 2, el usuario no tiene una forma sencilla de saber si una función que no aparece en la lista es determinista o no (podría ser de categoría 1 o 3). Por ejemplo, torch.add
no aparece en la lista, entonces, ¿cómo sabe el usuario que es determinista?
Quizás también podríamos pensar en mantener una lista de funciones de categoría 3. Pero mantener manualmente estas listas sería muy difícil por muchas razones, así que me pregunto si podríamos automatizarlo un poco. Potencialmente, podríamos configurar un trabajo de CI que ejecute pruebas de determinismo en todas las funciones. No es posible probar al 100% de manera inductiva que una función es determinista, y una función no determinista a veces puede dar el mismo resultado varias veces si no tenemos suerte. Pero cuanto más a menudo ejecutemos estas pruebas, más confianza tendremos en la categoría de la que forma parte cada función.
También está la cuestión de cómo transmitir al usuario de la manera más eficiente todo lo que sabemos y no sabemos sobre cada función y cada plataforma. Quizás podríamos hacer una tabla de todas las funciones de categoría 2 y 3 en cada plataforma. Sería bueno si las pruebas de determinismo pudieran verificar automáticamente que esta tabla es correcta.
Solo una lluvia de ideas, tal vez estas ideas sean más difíciles de lo que valen. Un plan más pragmático podría ser significativamente más sostenible, aunque menos ideal.
¿Es torch.add
determinista?
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')
Probablemente deberíamos documentar que los tensores superpuestos violan cualquier contrato de determinismo que pretendamos.
Enumerar las operaciones afectadas por una bandera de "determinismo" suena bien. Sin embargo, retrocediendo un poco, parece que realmente estamos hablando de dos cosas:
use_deterministic
?)Una bandera para lo primero parece sencillo. El segundo, sin embargo, es un poco más complicado. Me preocupa que sea difícil saber si las operaciones de las bibliotecas matemáticas como oneDNN, cuDNN y MAGMA son deterministas, especialmente en las versiones y el hardware. ¿Tiene una idea de la mejor manera de abordar esto, @kurtamohler? ¿Quizás podríamos advertir sobre todas las operaciones nativas no deterministas y también advertir cuando se realizaron llamadas a la biblioteca matemática también? Advertir una vez por proceso no debería ser tan intrusivo.
Este enfoque de las advertencias requeriría revisar muchos algoritmos y sitios de llamadas antes de publicarlos, pero no es necesario que bloquee la bandera para seleccionar algoritmos deterministas si están disponibles.
(Una tercera cosa en discusión es la mejor manera de presentar la selección de algoritmo determinista (a través de una bandera global o como kwargs en funciones), pero creo que podemos retrasar esa discusión hasta que determinemos un plan para la (s) bandera (s)?)
Creo que aquí no deberíamos dejar que lo perfecto sea enemigo de lo bueno. No sé cuándo fue 100% seguro usar tensores superpuestos con PyTorch, y mi impresión es que no es que la gente común los use.
Mi impresión de los foros es que la mayoría de la gente se sorprende de que ejecuten algo dos veces y obtengan diferentes gradientes, la mayoría de las veces debido a una de las funciones nativas de PyTorch que usa atomicAdd.
Si recibimos advertencias por eso, hemos cubierto la mayoría de los casos sobre los que la gente se está preguntando. Algo que se siente como la mitad es en realidad de un retroceso de escala.
Creo que deberíamos decir claramente que este es el mejor esfuerzo en lo que respecta a las bibliotecas externas y que agregamos advertencias a medida que conocemos los problemas, pero mi impresión es que nuestros kernels nativos en realidad son lo que más importa.
No sé cuándo fue 100% seguro usar tensores superpuestos con PyTorch, y mi impresión es que no es que la gente común los use.
Sí, y cualquier programa que lo haga podría clasificarse razonablemente como erróneo. Solo quise decir que debemos tener cuidado de documentar cualquier contrato que se nos ocurra para estas banderas.
Creo que deberíamos decir claramente que este es el mejor esfuerzo en lo que respecta a las bibliotecas externas y que agregamos advertencias a medida que conocemos los problemas ...
¿El documento podría decir algo como "llamadas a bibliotecas matemáticas que se sabe que no son deterministas ..."?
Estoy de acuerdo con @ t-vi (y realmente me gusta la observación de que la mitad del no determinismo informado está retrocediendo). En particular, creo que un estado en el que tenemos funciones parcialmente documentadas que se sabe que son no deterministas (o incluso parcialmente documentadas algunas funciones como deterministas) es estrictamente mejor que uno en el que no damos ninguna indicación en absoluto: la clave es no pretender apoyar cosas que nosotros no apoyamos. Estoy de acuerdo en que es una actividad útil pensar en cómo se podría realizar la prueba del determinismo, pero creo que esto es una actividad ortogonal para marcar las API que obviamente no son deterministas.
Dado que muchas ideas han estado flotando, permítanme intervenir con mis pensamientos específicos sobre algunas de ellas:
No creo que debamos preocuparnos por el determinismo entre versiones / hardware, buena suerte con eso.
Cuando advertimos sobre el no determinismo, debería ser porque el no determinismo ESTÁ sucediendo, no porque PUEDE estar sucediendo. Si advierte en exceso, la gente comenzará a ignorar las advertencias.
parece complicado. Por ejemplo, ¿qué pasa si estoy ejecutando alguna operación y la implementación de PyTorch es determinista, pero alguna extensión ha anulado algo (a través de una tecla de envío, función de antorcha o de otra manera) y ahora no lo sé? Si esa es realmente la fuente de mi no determinismo, parece un fastidio no ser advertido.
Si esa es realmente la fuente de mi no determinismo, parece un fastidio no ser advertido.
Claro, pero el usuario también podría simplemente no involucrarnos en sus travesuras no deterministas, y luego, por supuesto, no esperaría ser advertido entonces;)
Creo que podemos cerrar este problema ahora, ya que la API de bandera existe y está bien documentada.
@kurtamohler Impresionante trabajo. Gracias.
¿Significa que podríamos usar torch.manual_seed(111)
para configurar todo determinista, incluida la operación interpolation
?
No. Eche un vistazo a la nota Reproducibilidad / Aleatoriedad .
Hasta ahora, tenemos infraestructura, marcamos las fuentes conocidas sobre el no determinismo y una documentación muy mejorada para que pueda saber lo que está sucediendo.
Si realiza operaciones no deterministas, todavía no tiene suerte, pero ahora es más razonable trabajar en ello.
La interpolación en particular parece algo que podría hacerse determinista escribiendo un kernel no tan complicado para los atrasados.
@ t-vi Hola, Ahora que se lanzó pytorch 1.7, ¿se ha actualizado el kernel de interpolación hacia atrás?
Entonces, los núcleos de muestreo ascendente de CUDA y hacia atrás se encuentran en aten/src/ATen/native/cuda/UpSample*
. Un grep sugiere que lineales, bilineales, cúbicos tienen un revés no determinista (tienen un marcador de advertencia), pero los más cercanos no.
Sin embargo,
Comentario más útil
Hola, quiero hablar sobre el plan en el futuro por
torch.deterministic
. Hay algunas preguntas de alto nivel que debemos responder:torch.deterministic
? ¿Qué espera el usuario? ¿Es el mejor esfuerzo realmente útil para un usuario? Si no es útil, ¿es mejor definirtorch.deterministic
en términos de qué operaciones controla?torch.deterministic
, ¿tiene sentido eliminar el argumento de palabra clavedeterministic=
completo de la API pública (bmm
, te estoy mirando)?Comenzando con (1), la documentación actual para torch.deterministic dice:
Si bien esto puede ser cierto para un eventual estado final, esto representa de manera inexacta la situación actual, donde muchas operaciones no han sido auditadas y para cualquier modelo dado, no sabemos si
torch.deterministic
realmente hará lo que hace. dice en la lata y hace que su modelo sea determinista / genere un error cuando presione nondet. Entonces, básicamente, nuestra implementación tiene errores con respecto a esta semántica, y seguirá teniendo errores en el futuro previsible. Este no es un gran estado para estar.Podríamos cambiar la documentación de torch.deterministic para mejorar esto. Algunos posibles cambios:
El segundo punto conduce a (2): si torch.deterministic ahora existe como una forma de alternar el determinismo, es mucho menos importante admitir el determinismo directamente en la API del usuario. Así que probablemente no deberíamos haber agregado el argumento
deterministic
a bmm. Podríamos considerar exponer una función interna si desea alternar algo directamente, perodeterministic
no debería estar disponible directamente en la función en sí.¿Qué piensas? Creo que cambiar los documentos es probablemente la forma más fácil de emprender un camino sostenible. Hay algunos otros detalles, como cómo completar la lista exhaustiva, pero esta semántica probablemente tenga más sentido que la semántica "ideal" que en realidad no va a ser cierta.
cc @gchanan @mruberry