Pytorch: RFC: agregue la bandera de antorcha determinista para forzar algoritmos deterministas

Creado en 18 dic. 2018  ·  67Comentarios  ·  Fuente: pytorch/pytorch

🚀 Característica

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.

Motivación

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.

Terreno de juego

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 .

cuDNN

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.

No goles

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:

  • Versión de PyTorch
  • Arquitectura de CPU (por ejemplo, x86 con AVX frente a ARM)
  • Arquitectura de GPU (por ejemplo, AMD frente a NVIDIA o P100 frente a V100)
  • Dependencias de la biblioteca (por ejemplo, OpenBLAS frente a MKL)
  • Número de subprocesos OpenMP

Sugerencias de implementación

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 .

Preguntas abiertas

¿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

feature high priority determinism internals triaged

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:

  1. ¿Cuál es la semántica de 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?
  2. Ahora que tenemos la bandera torch.deterministic , ¿tiene sentido eliminar el argumento de palabra clave deterministic= completo de la API pública ( bmm , te estoy mirando)?
  3. ¿Cuál es el juego final de este trabajo? ¿En cuánto de esto vas a trabajar tú (@kurtamohler), en comparación con la comunidad genérica, y cuando lleguemos al final de tu período aquí, cómo se ve un estado razonable?

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:

  • torch.deterministic es el mejor esfuerzo , pero informe los errores si ve que no detecta algo de no determinismo
  • torch.deterministic alterna el comportamiento de estos operadores (y luego da una lista exhaustiva de los operadores que alterna)

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

Todos 67 comentarios

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

  1. Lanza un error que le dice al usuario que configure la variable de manera apropiada.

  2. 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:

  • Para probar que el error se arroja en el caso correcto (cuda> = 10.2 y 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 prueba
  • Para asegurarnos de que las pruebas torch.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:

  1. ¿Cuál es la semántica de 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?
  2. Ahora que tenemos la bandera torch.deterministic , ¿tiene sentido eliminar el argumento de palabra clave deterministic= completo de la API pública ( bmm , te estoy mirando)?
  3. ¿Cuál es el juego final de este trabajo? ¿En cuánto de esto vas a trabajar tú (@kurtamohler), en comparación con la comunidad genérica, y cuando lleguemos al final de tu período aquí, cómo se ve un estado razonable?

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:

  • torch.deterministic es el mejor esfuerzo , pero informe los errores si ve que no detecta algo de no determinismo
  • torch.deterministic alterna el comportamiento de estos operadores (y luego da una lista exhaustiva de los operadores que alterna)

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:

  1. Determinista
  2. No determinista de forma predeterminada, pero tiene soporte para el indicador determinista (ya sea de error o implementación alternativa)
  3. No determinista y no tiene soporte para la bandera determinista

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:

  • Solicitar versiones deterministas de operaciones, si están disponibles ( use_deterministic ?)
  • Advertencia si una operación no es determinista

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:

  1. "Quizás también podríamos pensar en mantener una lista de funciones de categoría 3". Esto parece mucho trabajo. Creo que probablemente solo valga la pena para las funciones en las que hicimos explícitamente algunas adaptaciones para el determinismo (lo más probable es que las funciones que apoyan la bandera determinista)
  2. "Podríamos configurar potencialmente un trabajo de CI que ejecute pruebas de determinismo en todas las funciones". Creo que algo como esto tendría que hacerse con mucho cuidado, porque por su propia naturaleza está probando algo que no es determinista, y eso significa que la prueba de determinismo en sí es "inestable" (pasará algunas veces y fallará otras) . Nuestras herramientas de informes de CI no manejan muy bien situaciones como esta.
  3. "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". Deberíamos esforzarnos mejor en esto. En muchos casos, la biblioteca matemática especifica explícitamente en su documentación que son deterministas o no, y simplemente deberíamos informar fielmente lo que dicen los documentos.
  4. "¿Quizás podríamos advertir sobre todas las operaciones nativas no deterministas y también advertir cuando se hicieron llamadas a la biblioteca matemática también?" No creo que debamos hacer esto. 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.

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,

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

NgPDat picture NgPDat  ·  3Comentarios

ikostrikov picture ikostrikov  ·  3Comentarios

soumith picture soumith  ·  3Comentarios

SeparateReality picture SeparateReality  ·  3Comentarios

cdluminate picture cdluminate  ·  3Comentarios