Numpy: RuntimeWarning sin sentido por np.float32 .__ mul__ compilado por clang

Creado en 27 abr. 2017  ·  53Comentarios  ·  Fuente: numpy/numpy

En Sagemath nos encontramos en nuestro ticket # 22799

RuntimeWarning: invalid value encountered in multiply

mientras se multiplica un numpy.float32 con un dato no numérico; es decir, numpy debería fallar en hacer esta multiplicación silenciosamente, y de hecho lo hace si uno construye con gcc, o si en lugar de np.float32 es np.float o np.float128 .

Más precisamente, uno recibe la advertencia de la llamada de Python

type(numpy.float32('1.5')).__mul__(numpy.float32('1.5'), x)

donde x es un polinomio univariado de Sagemath con coeficientes en el tipo RealField Sagemath. (y solo este tipo particular de datos desencadena esto).
Es decir, potencialmente, tales advertencias sin sentido pueden emitirse fuera de Sagemath; podemos reproducirlo en OSX 11.12 con su stock cc (algún derivado de clang 3.8), así como en Linux con clang 4.0 y en FreeBSD 11.0 con clang 4.0 o clang 3.7.

Potencialmente, deberíamos ser capaces de producir una forma de reproducir esto fuera de Sagemath, aunque necesitaríamos algunos consejos en qué código numérico se implementa realmente este __mul__ , para ver qué funciones se aplican a x ...

Vemos esto en numpy 1.11 y también en 1.12.

Todos 53 comentarios

misma imagen con numpy.float32('1.5').__mul__(x) así como __add__ y __sub__ .

Este tipo de error es típico de matrices que contienen nan o infinity . ¿Qué devuelve np.array(x) ?

@ eric-wieser: devuelve array(x, dtype=object) , sin advertencias.

¿Da np.multiply(np.float32('1.5'), x) la misma advertencia?

numpy.multiply(numpy.float32('1.5'), x) da la misma advertencia.

¿Qué pasa con type(x).__rmul__(numpy.float32('1.5'), x) ?

Además, si pudiera ejecutar warnings.filterwarnings('error') , obtendría un seguimiento de pila completo

type(x).__rmul__(numpy.float32('1.5'), x)

TypeError: descriptor '__rmul__' requires a 'sage.structure.element.Element' object but received a 'numpy.float32'

x.__rmul__(numpy.float32('1.5')) bien.

Parece que olvidé cómo funciona rmul . Quise decir type(x).__rmul__(x, numpy.float32('1.5')) , pero me imagino que hace lo mismo que x.__rmul__ , a menos que x sea ​​realmente extraño

¿Esto también falla? np.multiply(np.array(1.5, dtype=object), x) (esta vez con filterwarnings , por favor)

type(x).__rmul__(x,numpy.float32('1.5')) pasa sin una advertencia.

Y, por cierto, configurar warnings.filterwarnings('error') no me da nada interesante,

---------------------------------------------------------------------------
RuntimeWarning                            Traceback (most recent call last)
<ipython-input-50-b3ece847d318> in <module>()
sage: np.multiply(np.array(1.5, dtype=object), x)
---------------------------------------------------------------------------
RuntimeWarning                            Traceback (most recent call last)
<ipython-input-52-706823a0b5a2> in <module>()
----> 1  np.multiply(np.array(RealNumber('1.5'), dtype=object), x)

RuntimeWarning: invalid value encountered in multiply

Hmm, Sage hizo algo que no esperaba allí. Sin embargo, supongo que el mismo comportamiento con float('1.5') .

Ok, esto es lo que creo que está sucediendo:

  • numpy está usando correctamente el bucle de objetos en ufunc, que acaba llamando PyNumber_Multiply
  • Dentro de sage , algo está configurando el indicador de error en la FPU (¿error en Sage?)
  • numpy está haciendo su verificación normal de la bandera fpu al salir de ufunc (¿error en los bucles de objetos?), Y encuentra el error dejado por sage
sage: float(1.5).__mul__(x)
NotImplemented
sage: np.float(1.5).__mul__(x)
NotImplemented
sage: np.float32(1.5).__mul__(x)
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x
sage: np.float64(1.5).__mul__(x)
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x

Vale la pena señalar que np.float is float por razones de compatibilidad

¿Por qué np.float32(1.5).__mul__(x) devuelve NotImplemented ?

Porque sabe que puede manejarlo como np.multiply con un bucle de objeto, y luego vuelve a intentarlo con float * x dentro de ese bucle. Desafortunadamente, la envoltura alrededor de ese bucle está recogiendo las banderas FPU establecidas por Sage.

Si observa de cerca, encontrará que x.__rmul__ todavía se está llamando más profundo en la pila

sage: np.float32(1.5)*x
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x
sage: np.float128(1.5)*x
1.50000000000000*x
sage: np.float64(1.5)*x
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x

por lo que parece que np.float128 está bien, pero

sage: np.float128(1.5).__mul__(x)
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x

Esto es raro. Un error del compilador, tal vez (como nunca lo vemos en gcc), pero ¿en qué lugar?

La bandera FPU por alguna razón está configurada en clang pero no en gcc dentro del código sabio, al parecer. Numpy tiene la culpa de hacer ruido al respecto, pero dudo mucho que tenga la culpa de establecerlo en primer lugar.

Desafortunadamente, numpy no expone ninguna forma de solicitar explícitamente las banderas de FPU; eso sería muy útil para dividir el problema en sage.

Supongo que esto causa la misma advertencia (creo que necesita 1.12 numpy para hacerlo)

mul = np.vectorize(x.__rmul__)
mul(float('1.5'))

no es lo mismo, pero cerca:

/usr/home/dima/Sage/sage/local/lib/python2.7/site-packages/numpy/lib/function_base.py:2652: RuntimeWarning: invalid value encountered in __rmul__ (vectorized)
  outputs = ufunc(*inputs)
array(1.50000000000000*x, dtype=object)

Bien, esto parece indicar que no hay nada específico con np.float32 o np.float64 , es un mecanismo numérico más genérico para generar advertencias que se activan aquí.

No sé si los autores del compilador lo considerarían un error. La forma en que funciona la advertencia es que hay algunas banderas de estado mágicas que el procesador realiza un seguimiento, que se configuran automáticamente cada vez que ocurre el evento correspondiente. Numpy los borra antes de comenzar el cálculo y luego los vuelve a verificar al final. Entonces, en algún lugar entre esos puntos, el ensamblaje generado por clang está haciendo un cálculo que involucra un NaN. Pero es difícil de rastrear (ya que la configuración real de la bandera se realiza completamente en hardware), y la mayoría de las veces la gente no se preocupa por cómo su código afecta las banderas de la fpu. (Las implementaciones de Libm también son notoriamente inconsistentes acerca de si establecen estos indicadores). Y los resultados exactos dependen mucho del conjunto exacto que se genera, por lo que no es sorprendente que solo lo vea en configuraciones específicas y no en otras.

Sí, eso confirma mis sospechas y le proporciona una forma de depurar. Este codigo

def check_fpu(f):
    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        excluded = list(range(len(args))) + list(kwargs.keys())
        fvec = np.vectorize(f, excluded=excluded)
        return fvec(*args, **kwargs)
    return wrapped

Aplicado a una función de Python, le permite aislar las advertencias dentro de ese fragmento de código.

Quiero decir, es bastante extraño que esté sucediendo; Los compiladores no suelen inventar y luego desechar los NaN sin ningún motivo.

Si está tratando de rastrearlo, entonces probablemente debería mirar el código en Sage que implementa la multiplicación para esos polinomios; es probable que la configuración de la bandera extraña esté sucediendo todo el tiempo, y la única participación de Numpy es hacer eso visible .

También hay un argumento bastante bueno de que numpy ni siquiera debería intentar verificar estas banderas en los bucles de objetos. (O bucles de enteros para el caso, pero eso es complicado porque la forma en que informamos el desbordamiento de enteros es algo asquerosa y usa las banderas fpu). Eso es lo único que puedo pensar que numpy podría hacer aquí.

check_fpu() tiene un error tipográfico, debería estar fvec = np.vectorize(f, excluded=exclude) allí.
Y estamos en python2: import functools32 as functools .

functools.wraps no necesita Python 3, ¿verdad?

Recibo un error si uso las funciones de python2, en setattr() call

AttributeError: 'method-wrapper' object has no attribute '__module__'

Sí, supongo que es cualquier biblioteca de precisión múltiple que implemente la aritmética para los coeficientes en RealField que esté configurando la bandera FPU. ¿Las bibliotecas subyacentes se compilan con el mismo compilador que numpy en cada una de las diferentes circunstancias? ¿O solo se está reconstruyendo numpy con los diferentes compiladores?

Sí, supongo que es cualquier biblioteca de precisión múltiple que implemente la aritmética para los coeficientes en RealField

Eso es MPFR para el registro.

Intentamos portar Sagemath a clang + gfortran (principalmente en OSX y FreeBSD, plataformas donde clang es el compilador principal), para que construirlo y ejecutarlo en OSX sea más fácil y rápido (FreeBSD es más una herramienta para obtener un entorno similar sin la molestia del hardware OSX y Apple).

Todas las comparaciones que informo aquí son para compilaciones completas con clang / clang +++ gfortran en lugar de gcc / g +++ gfortran.

la envoltura parece decirnos que x.__rmul__ configura la bandera FPU

check_fpu(x.__rmul__)(np.float32('1.5'))

imprime la advertencia, mientras que x.__rmul__(np.float32('1.5')) no.

De hecho, mi suposición fue que x.__rmul__ se escribió en Python y que su código fuente se puede dividir en dos para encontrar qué bit establece específicamente la bandera

x.__rmul__ está en Cython, pero aún es un pequeño fragmento de código para investigar.

Si hubiera una forma sencilla de cambiar la advertencia por un error, obtendría un rastreo (Cython genera rastreo para errores pero no para advertencias).

@jdemeyer En mi humilde opinión, la advertencia numpy se emite mucho más tarde en la ruta del código, es decir, es el resultado de una verificación explícita de las banderas FPU, no un conjunto de interrupciones.

numpy proporciona una interfaz para cambiar esta advertencia a un error, pero todo lo que obtiene es que vuelve al bucle principal del intérprete de iPython, sin ningún tipo de retroceso.

@jdemeyer, ¿el código Cython entre sig_on() / sig_off() de cysignals produciría una excepción si se

cysignals lanzaría una excepción si se activa SIGFPE , lo que puede suceder si se activa una bandera FPU, dependiendo de la configuración de la FPU. Pero por defecto no es así.

Una advertencia similar: RuntimeWarning: invalid value encountered in greater es
procedente de np.float64(5)>e . Aquí e es la constante de Sagemath que especifica la base del logaritmo natural 2.71828 ..., y así, en el camino de evaluar esto a True tiene que ser "convertido" (ciertamente, e "sabe "su aproximación numérica, es e.n() ) a un número.
Esta aproximación es del tipo RealField ya mencionado anteriormente (por lo que quizás esta advertencia esté estrechamente relacionada).

Una vez más, la pregunta es: ¿qué hace numpy para evaluar np.float64(5)>e ?
O de manera equivalente, la misma advertencia aparece desde np.float64(5).__gt__(e) , por lo que también se puede comenzar desde allí.

Tenga en cuenta que type(e) es sage.symbolic.constants_c.E ; es básicamente una clase (casi) ficticia
envolviendo las expresiones simbólicas de Sagemath ( SR ).

No hay advertencias de np.float64(5).__gt__(e.n()) o np.float64(5)>e.n() .
Esencialmente lo mismo (el mismo patrón de advertencia / sin advertencia) sucede si reemplaza e por pi (con pi.n()==3.1.415... obvios).
pi tiene el tipo SR , es decir, sage.symbolic.expression.Expression .

La respuesta es la misma aquí: numerosas llamadas np.greater con un bucle de objeto. En el nivel inferior, eso llama e.__lt__(5.0) . Pero una vez más, comprueba las banderas de FPU antes y después, y se da cuenta de que algo anda mal.

La mayoría de los operadores lógicos / aritméticos de ndarray (con la excepción de - y divmod ) delegan en ufuncs. Cuando se invoca con objetos sage, esto llamará a los bucles O (object) para estos ufuncs. Estos bucles de objetos recorrerán la matriz (que en este caso es 0d), ejecutarán el operador normal de Python en los elementos, _pero verificará los indicadores FPU cuando lo haga_.

Así que, una vez más, Sage está poniendo estas banderas. Quizás esto sea una señal de un error, quizás no.

Creo que hay un buen argumento aquí de que numpy no debería comprobar las banderas de fpu para estos casos. @njsmith , ¿cree que deberíamos continuar con la eliminación de la verificación de tipos de objetos?

De hecho, e.__lt__(5.0) es una expresión simbólica:

sage: type(e.__lt__(np.float32(5.0)))
<type 'sage.symbolic.expression.Expression'>
sage: e.__lt__(np.float32(5.0))
e < 5.0
sage: bool(e.__lt__(np.float32(5.0)))  # this is how it's evaluated
True

y por lo tanto dudo mucho que se llame al final, porque uno obtiene True . Además, su envoltorio check_fpu de arriba no hace que imprima advertencias, es decir, lo siguiente simplemente funciona.

sage: check_fpu(e.__lt__)(np.float32(5.0))
e < 5.0

Pude ubicar nuestro problema en Sagemath en una extensión C particular usando el módulo fpectl Python (que está algo, pero no totalmente, roto en FreeBSD). En realidad, fue muy rápido una vez que logré instalarlo.

En mi humilde opinión, fpectl es tan útil que debería arreglarse ; quizás incluso se use en numpy en lugar de, o además de, np.seterr() , ya que proporciona una mejor granularidad en los componentes compilados.

La diferencia entre el enfoque de fpectl y np.seterr es:

np.seterr ejecuta el bucle ufunc y luego verifica si hay indicadores establecidos.

fpectl hace algo de magia para que cada vez que ocurra una operación que haga que se establezca uno de los indicadores, el hardware genere una interrupción, el kernel lo convierta en un SIGFPE entregado al proceso e instale un Manejador SIGFPE que longjmp s directamente desde el manejador de señales hacia el código de manejo de errores.

Algunas desventajas del enfoque fpectl son: (a) no funciona en absoluto en Windows, (b) se rompe para el código que internamente hace que uno de estos indicadores se establezca temporalmente y luego lo borra (esto es legal y espero que haya libm que lo hagan), (c) longjmp es increíblemente frágil; básicamente, corre el riesgo de una falla secundaria cada vez que lo hace. Ciertamente, no puede ser una solución general para ufuncs arbitrarios definidos por el usuario.

Dado todo esto, no creo que Numpy vaya a cambiar.

En cualquier caso, parece que el problema original está resuelto, así que cerrando esto, siéntase libre de abrir un nuevo problema si desea presentar un caso de cambios en seterr .

En cualquier caso, parece que el problema original está resuelto.

¿Estamos seguros de que no queremos deshabilitar la verificación de las banderas FPU para bucles de objetos? Eso parecería un cambio bastante sensato a numpy.

@ eric-wieser: oh, esa es una idea interesante, sí. tal vez valga la pena abrir un problema para eso :-). Sin embargo, lo "correcto" es bastante complicado; idealmente, no deberíamos poner en mayúsculas y minúsculas el tipo de objeto dtype (piense en los tipos de usuario de usuario), y los bucles de números enteros tampoco deberían usarlo (esto puede ser una optimización real en algunas arquitecturas donde se verifica / borrar las banderas de FPU es extremadamente lento), pero los bucles de números enteros necesitan una forma de señalar explícitamente los errores de números enteros, lo que actualmente hacen estableciendo explícitamente las banderas de FPU, ... no estoy seguro de que este sea un caso en el que haya un nivel bajo fácil - ¿Fruta colgante?

¿O lo entendí mal y Sage solo identificó el problema, y ​​todavía necesitan un cambio considerable para solucionarlo?

@njsmith : No entiendo por qué dice que no funcionará en Windows. (Sin embargo, esto sería correcto en la era anterior a C99). Las funciones modernas de manejo de FPU (fenv) están disponibles tan pronto como su compilador de C cumpla con el estándar C99. Aparte de fenv, todo lo que necesita es setjmp / longjmp (nuevamente, característica estándar de C).

También tengo curiosidad por saber de un libm que provoca una de las excepciones de
(A menos que esté clasificado como error).

@dimpase : también necesita compatibilidad con SIGFPE, que no se especifica en C99. (Bueno, C99 dice que debería haber un SIGFPE, pero eso es para dividir por cero; no especifica ninguna forma de conectarlo a excepciones de punto flotante). Dicho esto, parece que no recordé bien, y aunque Windows no admite señales, MSVCRT emula SIGFPE mediante el manejo estructurado de excepciones y proporciona la función no estándar _control_fp para habilitarla para excepciones de fp particulares, por lo que la compatibilidad con Windows no es realmente una barrera. OTOH, no importa mucho ya que longjmp definitivamente no está sucediendo sin una muy buena razón :-)

Y FWIW, si un libm causó una excepción FE y luego la borró nuevamente, no veo por qué lo considerarían un error. No estoy seguro de que existan tales implementaciones, pero es plausible, y si lo hacen, entonces la forma en que lo descubriríamos es porque alguien nos dice que numpy está roto en la plataforma X y la única solución sería revertir el cambio. sugeriste.

¿Puedes responder la pregunta que hice al final de mi comentario anterior?

@njsmith : si una libm (o cualquier otro código de usuario) necesita causar una excepción FE y procesarla, configuraría su propio controlador de excepciones FE, guardando el anterior y restaurando el anterior al salir.
Por lo tanto, no es un problema si el código subyacente sigue las reglas.

Con respecto al soporte de MS para esto, envían fenv.h desde Visual C (++) 2013 más o menos .
Está específicamente destinado a ser útil con setjmp / longjmp (espero que no se preocupe demasiado por cómo se hace exactamente bajo el capó).

Con respecto al tiempo de ejecución de numpy

  • Si cree que el código de usuario puede reproducirse rápido y suelto con banderas FP, entonces estas advertencias deberían ser opcionales como máximo.
  • de lo contrario, se pueden mantener estándar, pero al menos una forma de llegar al fondo del lugar de donde provienen es crucial para que sean útiles; (mejorado) fpectl es una forma rápida de lograr este último. El uso de herramientas externas (como depuradores que le permiten instrumentar el código para verificar algo después de cada instrucción) es más difícil, más lento y más propenso a errores, por ejemplo, no es inusual que el error solo aparezca en un binario optimizado y desaparece tan pronto como intentas encontrarlo en un binario bien depurable.
  • en cualquier caso, la cosa RuntimeWarning debería poder desactivarse.

Con respecto a este problema en Sage, aún se está solucionando (es de esperar que se limite a algunos problemas solo en MPFR ).

Lo siento, esto está dando vueltas y necesito pasar a otras cosas, así que a menos que surja algo nuevo sobre el tema fenv / sigfpe, este será mi último mensaje sobre el tema. (Todavía estoy interesado en saber si hay algo que Numpy deba hacer para el error de la salvia).

si una libm (o cualquier otro código de usuario) necesita causar una excepción FE y procesarla, configuraría su propio manejador de excepciones FE, guardando el anterior,

Lo que está proponiendo es realizar una operación que normalmente no provoca que se active un manejador de señales y configurar el procesador en un modo no estándar en el que sí provoca que se active un manejador de señales. Es totalmente razonable que el código realice esta operación y espere que no active un controlador de señal en absoluto.

Con respecto al soporte de MS para esto, envían fenv.h desde Visual C (++) 2013 más o menos.
Está específicamente destinado a ser útil con setjmp / longjmp

No puedo entender de qué estás hablando aquí. Afaict, la funcionalidad estándar en fenv.h solo es útil para implementar la funcionalidad de estilo numpy, y MS se adhiere al estándar. No veo ninguna función allí que pueda usarse con setjmp / longjmp en absoluto.

El código de usuario puede jugar rápido y suelto con banderas FP, entonces estas advertencias deberían ser opcionales como máximo.

Limpiar cuidadosamente una bandera establecida por un cálculo intermedio es exactamente lo opuesto a jugar rápido y relajado con ellos. Además, las advertencias son opcionales.

una forma de llegar al fondo del lugar de donde provienen es crucial para que sean útiles; (mejorado) fpectl es una forma rápida de lograr este último.

Eres literalmente la primera persona en algo así como una década que necesita SIGFPE para depurar este tipo de problema, y ​​al mirar de nuevo los comentarios de errores de sabio, parece que en realidad no conseguiste que fpectl funcione No se supone que cause un volcado de memoria. (Parece que cysignals está anulando el código fpectl, por lo que ni siquiera se ejecuta).

Si esto vuelve a surgir, lo que debe hacer es realizar una llamada C para habilitar SIGFPE y luego usar un depurador para obtener un seguimiento de la pila. No necesita una compilación de depuración para obtener un seguimiento de la pila; todo lo que necesita hacer es no quitar los símbolos. Y bueno, ahora lo sabemos en caso de que esto vuelva a surgir.

Entiendo que esto fue realmente frustrante de depurar, pero no es útil insistir en que otros proyectos deben cambiar o mantener la infraestructura básica cuando ni siquiera puedes explicar claramente lo que esto logrará. (De hecho, no tengo idea de cómo crees que cambiar algo aquí te ayudaría a encontrar este tipo de error más rápido; la idea de longjmp es destruir la información más precisa que dices que quieres).

Finalmente, resulta que se reduce a un error de larga data en el compilador C de clang. Básicamente, en un cierto rango de double una asignación como en

unsigned long a;
double b;
...
a = b;

aumenta FE_INVALID (ya veces FE_INEXACT ); Por cierto, esto también está afectando a otros tipos de datos flotantes. Excelente MPFR (MPFR tiene que copiar dobles en sus flotadores de precisión arbitrarios, ciertamente) la gente proporcionó una solución alternativa, arreglando esto para Sage.

Por cierto, un error de clang fenv.h de clang para hacer que fpectl funcione correctamente en este momento . Clang no es completamente compatible con C99 a este respecto, y está siendo muy tímido al respecto.

No estoy seguro de si numpy quiere hacer algo al respecto; quizás un comentario de que el RuntimeWarning podría deberse simplemente a un error del compilador (citando "clang bug 8100", es un ejemplo bastante destacado) podría ser útil.

el error 8100 no es relevante; eso es para que los pragmas C99 deshabiliten las optimizaciones de punto flotante, y ningún compilador convencional las admite. numpy parece funcionar (principalmente) de todos modos :-)

El espíritu del error 8100 es que a clang no le importa que las operaciones de FP se compilen correctamente; aunque un abogado puede estar en desacuerdo. :-)

De acuerdo, el error 17686 ya mencionado es relevante con seguridad.

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