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

Criado em 27 abr. 2017  ·  53Comentários  ·  Fonte: numpy/numpy

Em Sagemath encontramos em nosso tíquete # 22799

RuntimeWarning: invalid value encountered in multiply

ao multiplicar um número numpy.float32 com dados não numpy; isto é, numpy deveria falhar em fazer esta multiplicação silenciosamente, e de fato falha se alguém construir com gcc, ou se ao invés de np.float32 for np.float ou np.float128 .

Mais precisamente, obtém-se o aviso da chamada Python

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

onde x é um polinômio univariado de Sagemath com coeficientes no tipo RealField Sagemath. (e apenas este tipo específico de dados aciona isso).
Ou seja, potencialmente, tais avisos sem sentido podem ser emitidos fora de Sagemath; podemos reproduzi-lo no OSX 11.12 com seu cc original (algum derivado do clang 3.8), bem como no Linux com clang 4.0 e no FreeBSD 11.0 com clang 4.0 ou clang 3.7.

Potencialmente, deveríamos ser capazes de produzir uma maneira de reproduzir isso fora de Sagemath, embora precisemos de algumas dicas onde em código numpy este __mul__ é realmente implementado, para ver quais funções são aplicadas a x ...

Vemos isso no numpy 1.11 e no 1.12 também.

Todos 53 comentários

mesma imagem com numpy.float32('1.5').__mul__(x) , bem como __add__ e __sub__ .

Esse tipo de erro é típico de arrays que contêm nan ou infinity . O que np.array(x) retorna?

@ eric-wieser: retorna array(x, dtype=object) , sem avisos.

np.multiply(np.float32('1.5'), x) dá o mesmo aviso?

numpy.multiply(numpy.float32('1.5'), x) dá o mesmo aviso.

E quanto a type(x).__rmul__(numpy.float32('1.5'), x) ?

Além disso, se você pudesse executar warnings.filterwarnings('error') , você obteria um rastreamento de pilha 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')) vai bem.

Parece que esqueci como funciona rmul . Eu quis dizer type(x).__rmul__(x, numpy.float32('1.5')) , mas imagino que faça a mesma coisa que x.__rmul__ , a menos que x seja realmente estranho

Isso também falha? np.multiply(np.array(1.5, dtype=object), x) (desta vez com filterwarnings , por favor)

type(x).__rmul__(x,numpy.float32('1.5')) passa sem um aviso.

E, a propósito, definir warnings.filterwarnings('error') não me dá nada de interessante,

---------------------------------------------------------------------------
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 fez algo que eu não esperava lá. Mesmo assim com float('1.5') , suponho?

Ok, então aqui está o que eu acho que está acontecendo:

  • numpy está usando corretamente o loop de objeto no ufunc, que acaba chamando apenas PyNumber_Multiply
  • Dentro de sage , algo está definindo o sinalizador de erro na FPU (bug no sage?)
  • numpy está fazendo sua verificação normal de sinalizador de fpu ao sair do ufunc (bug em loops de objeto?) E encontrando o erro deixado 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 a pena notar que np.float is float por razões de compatibilidade

por que np.float32(1.5).__mul__(x) retorna NotImplemented ?

Porque ele sabe que pode tratá-lo como np.multiply com um loop de objeto, e então tenta novamente com float * x dentro desse loop. Infelizmente, o envoltório em torno desse loop está pegando os sinalizadores de FPU definidos pelo sábio.

Se você olhar com atenção, verá que x.__rmul__ ainda está sendo chamado no nível mais profundo da pilha

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

então parece que np.float128 está OK, mas

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

Isso é estranho. Um bug do compilador, talvez (como nunca o vemos no gcc), mas em que lugar?

O sinalizador FPU está, por algum motivo, sendo definido no clang, mas não no gcc dentro do código sage, ao que parece. Numpy é o culpado por fazer barulho sobre isso, mas eu duvido muito que seja o culpado por tê-lo feito em primeiro lugar.

Infelizmente, numpy não expõe nenhuma maneira de solicitar explicitamente os sinalizadores de FPU - isso seria muito útil para dividir o problema em dois.

Presumo que isso cause o mesmo aviso (é necessário numpy 1.12 para fazer isso, eu acho)

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

não exatamente o mesmo, mas perto:

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

OK, bom, isso parece indicar que não há nada específico com np.float32 ou np.float64 , é um mecanismo numpy mais genérico para gerar avisos aqui.

Não sei se os autores do compilador considerariam isso um bug. A forma como o aviso funciona é que existem alguns sinalizadores de status mágicos que o processador mantém controle, que são definidos automaticamente sempre que o evento correspondente ocorre. Numpy os apaga antes de iniciar o cálculo e, em seguida, verifica-os novamente no final. Portanto, em algum lugar entre esses pontos, a montagem gerada pelo clang está fazendo alguns cálculos que envolvem um NaN. Mas é difícil rastreá-lo (uma vez que a configuração real dos sinalizadores é feita inteiramente no hardware) e, na maioria das vezes, as pessoas não se preocupam sobre como seu código afeta os sinalizadores de fpu. (As implementações de Libm também são notoriamente inconsistentes quanto ao fato de definirem esses sinalizadores.) E os resultados exatos dependem muito do conjunto exato que está sendo gerado, portanto, não é surpreendente que você só o veja em configurações específicas e não em outras.

Sim, isso confirma minhas suspeitas e fornece uma maneira de depurar. Este código

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 uma função python, permite que você isole os avisos para dentro desse pedaço de código.

Quer dizer, é muito estranho que isso esteja acontecendo; compiladores geralmente não inventam e depois jogam fora NaNs sem motivo.

Se você está tentando rastreá-lo, você provavelmente deve olhar para o código no sage que implementa a multiplicação para esses polinômios - é provável que a configuração de sinalizador estranho esteja acontecendo o tempo todo, e o único envolvimento de numpy é torná-lo visível .

Também há um bom argumento de que o numpy não deve nem tentar verificar esses sinalizadores em loops de objeto. (Ou loops inteiros para esse assunto, mas isso é complicado porque a maneira como relatamos o estouro de inteiros é meio grosseira e usa os sinalizadores de fpu.) Essa é a única coisa que posso pensar que numpy poderia fazer aqui.

check_fpu() tem um erro de digitação, deveria estar fvec = np.vectorize(f, excluded=exclude) ali.
E estamos em python2: import functools32 as functools .

functools.wraps não precisa do python 3, certo?

Recebo um erro se usar as functools do python2, na chamada setattr()

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

Sim, estou supondo que seja qualquer biblioteca de multiprecisão implementando a aritmética para os coeficientes em RealField que está definindo o sinalizador FPU. As bibliotecas subjacentes são compiladas com o mesmo compilador como numpy em cada uma das diferentes circunstâncias? Ou apenas o numpy está sendo reconstruído com os diferentes compiladores?

Sim, estou supondo que seja qualquer biblioteca de multi-precisão que implementa a aritmética para os coeficientes em RealField

Isso é MPFR para o registro.

Tentamos portar Sagemath para clang + gfortran (principalmente no OSX e FreeBSD, plataformas onde o clang é o compilador principal), para que construí-lo e executá-lo no OSX seja mais fácil e rápido (o FreeBSD é mais uma ferramenta para obter um ambiente semelhante sem o incômodo de hardware OSX e Apple).

Todas as comparações que relato aqui são para compilações completas com clang / clang +++ gfortran em oposição a gcc / g +++ gfortran.

o wrapper parece nos dizer que x.__rmul__ configura o sinalizador FPU

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

imprime o aviso, enquanto x.__rmul__(np.float32('1.5')) não.

Na verdade - minha suposição era que x.__rmul__ foi escrito em python e que seu código-fonte pode ser dividido ao meio para encontrar qual bit define especificamente o sinalizador

x.__rmul__ está no Cython, mas ainda é um pequeno trecho de código para investigar.

Se houvesse uma maneira simples de alterar o aviso para um erro, você obteria um traceback (o Cython gera rastreamentos para erros, mas não para avisos).

@jdemeyer IMHO numpy warning é emitido muito mais tarde no caminho do código, ou seja, é o resultado de uma verificação explícita de sinalizadores FPU, não um conjunto de interrupções.

O numpy fornece uma interface para transformar esse aviso em um erro, mas tudo o que você obtém é voltar ao loop do interpretador principal do iPython, sem nenhum backtrace.

@jdemeyer o código Cython entre sig_on() / sig_off() de cysignals lançaria uma exceção se um sinalizador FPU fosse gerado? Ou depende da bandeira?

cysignals lançaria uma exceção se SIGFPE fosse levantado, o que pode acontecer se um flag de FPU for gerado, dependendo da configuração da FPU. Mas, por padrão, isso não acontece.

Um aviso semelhante: RuntimeWarning: invalid value encountered in greater é
vindo de np.float64(5)>e . Aqui e é a constante Sagemath especificando a base do logaritmo natural 2.71828 ..., e assim, no caminho para avaliar isso para True ela deve ser "convertida" (certamente, e "sabe "sua aproximação numérica, é e.n() ) para um número.
Esta aproximação é do tipo RealField já mencionado acima (portanto, talvez este aviso esteja intimamente relacionado).

Novamente, a questão é: o que numpy faz para avaliar np.float64(5)>e ?
Ou, de forma equivalente, o mesmo aviso aparece em np.float64(5).__gt__(e) , portanto, pode-se muito bem começar a partir daí.

Observe que type(e) é sage.symbolic.constants_c.E ; é basicamente uma aula (quase) fictícia
envolvendo as expressões simbólicas de Sagemath ( SR ).

Não há avisos de np.float64(5).__gt__(e.n()) ou np.float64(5)>e.n() .
Essencialmente, o mesmo (mesmo padrão de aviso / sem aviso) acontece se você substituir e por pi (com pi.n()==3.1.415... óbvio).
pi tem o tipo SR , ou seja, sage.symbolic.expression.Expression .

A resposta é a mesma aqui - numpy chama np.greater com um loop de objeto. No nível inferior, isso chama e.__lt__(5.0) . Mas, mais uma vez, ele verifica os sinalizadores da FPU antes e depois e percebe que algo está errado.

A maioria dos operadores aritméticos / lógicos ndarray (com exceção de - e divmod ) delega para ufuncs. Quando invocado com objetos sábios, isso chamará os loops O (objeto) para esses ufuncs. Esses loops de objeto farão um loop sobre o array (que neste caso é 0d), execute o operador Python normal nos elementos, _mas verifique se há sinalizadores de FPU quando isso ocorrer_.

Então, mais uma vez, o sábio está definindo essas bandeiras. Talvez isso seja um sinal de um bug, talvez não seja.

Acho que há um bom argumento aqui de que o numpy não deve verificar os sinalizadores de fpu nesses casos. @njsmith , você acha que devemos prosseguir com a remoção da verificação de tipos de objeto?

Na verdade, e.__lt__(5.0) é uma expressão 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

e, portanto, realmente duvido que seja chamado no final, pois alguém obtém True . Além disso, seu invólucro check_fpu acima não faz com que ele imprima avisos, ou seja, o seguinte simplesmente funciona.

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

Consegui identificar nosso problema no Sagemath em uma extensão C específica usando o módulo Fpectl Python (que está um tanto, mas não totalmente, quebrado no FreeBSD). Na verdade, foi muito rápido depois que consegui instalá-lo.

IMHO fpectl é tão útil que deveria ser consertado ; talvez até mesmo usado em numpy em vez de, ou além de np.seterr() , pois fornece melhor granularidade nos componentes compilados.

A diferença entre a abordagem de fpectl e np.seterr é:

np.seterr executa o loop ufunc e, em seguida, verifica se algum sinalizador está definido.

fpectl faz alguma mágica para torná-lo de forma que sempre que ocorrer uma operação que causa a ativação de um dos sinalizadores, o hardware gera uma interrupção, o kernel converte isso em um SIGFPE entregue ao processo e instala um SIGFPE manipula que longjmp s diretamente do manipulador de sinal para o código de tratamento de erro.

Algumas desvantagens da abordagem fpectl são: (a) ele não funciona no Windows, (b) ele quebra para o código que faz com que um desses sinalizadores seja temporariamente definido internamente e, em seguida, o apaga (este é legal e espero que existam libm's que o façam), (c) longjmp é incrivelmente frágil; basicamente, você está arriscando um segfault toda vez que faz isso. Certamente não pode ser uma solução geral para ufuncs arbitrários definidos pelo usuário.

Diante de tudo isso, não acho que entorpecido vá mudar.

Em qualquer caso, parece que o problema original foi resolvido, portanto, fechando este - sinta-se à vontade para abrir um novo problema se quiser fazer um caso para alterações em seterr .

Em qualquer caso, parece que o problema original foi resolvido

Temos certeza de que não queremos desabilitar a verificação dos sinalizadores de FPU para loops de objeto? Isso pareceria uma mudança bastante sensata para entorpecido.

@ eric-wieser: oh, essa é uma ideia interessante, sim. talvez valha a pena abrir um problema para isso :-). A "coisa certa" é bem complicada - o ideal é que não devamos criar uma caixa especial para o tipo de objeto (pense em tipos de usuário), e loops inteiros também não deveriam usá-lo (isso pode ser uma otimização real em algumas arquiteturas onde verificar / limpar os sinalizadores de FPU é extremamente lento), mas os loops de inteiros precisam de uma maneira de sinalizar explicitamente os erros de inteiros, o que eles fazem atualmente definindo explicitamente os sinalizadores de FPU, ... Não tenho certeza se este é um caso em que há baixo fácil - mudando de fruta?

Ou eu entendi mal, e a sage apenas identificou o problema e eles ainda precisam de uma mudança numpy para realmente corrigi-lo?

@njsmith : Não entendo por que você diz que não funciona no Windows. (Isso seria correto na era pré-C99, no entanto). As funções modernas de manipulação de FPU (fenv) estão disponíveis assim que seu compilador C for compatível com o padrão C99. Além do fenv, tudo que ele precisa é setjmp / longjmp (novamente, recurso C padrão).

Também estou curioso para saber sobre uma libm que causa uma das exceções FE no curso de uma operação normal.
(A menos que seja classificado como um bug).

@dimpase : Você também precisa do suporte SIGFPE, que não é especificado no C99. (Bem, C99 diz que deveria haver um SIGFPE, mas isso é para dividir por zero - ele não especifica nenhuma maneira de conectá-lo a exceções de ponto flutuante.) Dito isso, parece que me lembrei mal, embora o Windows não oferece suporte a sinais, o MSVCRT emula SIGFPE usando tratamento de exceção estruturado e fornece a função _control_fp não padrão para habilitá-lo para exceções fp específicas, portanto, o suporte do Windows não é realmente uma barreira. OTOH, não importa muito, já que longjmp definitivamente não está acontecendo sem uma razão extremamente boa :-)

E FWIW, se uma libm causou uma exceção FE e depois a limpou novamente, não consigo ver porque eles considerariam isso um bug. Não tenho certeza se tais implementações existem, mas é plausível, e se existirem, a maneira que descobriríamos é porque alguém nos diz que o numpy está quebrado na plataforma X e a única solução seria reverter a mudança você sugeriu.

Você pode responder à pergunta que fiz no final do meu comentário anterior?

@njsmith : se uma libm (ou qualquer outro código de usuário) precisar causar uma exceção FE e processá-la, ele configurará seu próprio manipulador de exceção FE, salvando o anterior e restaurando o anterior ao sair.
Portanto, não é um problema se o código subjacente está jogando de acordo com as regras.

Em relação ao suporte da MS para isso, eles distribuem fenv.h desde Visual C (++) 2013 ou assim .
Ele foi projetado especificamente para ser útil com setjmp / longjmp (como exatamente ele é feito nos bastidores não deve preocupar muito, espero).

Em relação ao RuntimeWarning do numpy:

  • se você acha que o código do usuário pode ser executado de forma rápida e solta com sinalizadores de FP, esses avisos devem ser opcionais no máximo.
  • caso contrário, eles podem ser mantidos padrão, mas pelo menos uma maneira de chegar ao fundo do lugar de onde vêm é crucial para que sejam úteis; (melhorado) fpectl é uma maneira rápida de conseguir o último. Usar ferramentas externas (como depuradores, permitindo que você instrumentalize o código para verificar algo após cada instrução) é mais difícil, mais lento e mais sujeito a erros - por exemplo, não é incomum que o bug apareça apenas em um binário otimizado, e desaparece assim que você tenta encontrá-lo em um binário bem depurável.
  • em qualquer caso, deve ser possível desligar o RuntimeWarning.

Em relação a este problema no Sage - ainda corrigindo (espero que esteja limitado a alguns problemas apenas no MPFR ).

Desculpe, isso está acontecendo em círculos e eu preciso passar para outras coisas, então, a menos que algo novo apareça sobre o problema fenv / sigfpe, esta será minha última mensagem sobre o assunto. (Ainda estou interessado em saber se há algo entorpecido que precisa ser feito para o bug da sálvia).

se uma libm (ou qualquer outro código de usuário) precisa causar uma exceção FE e processá-la, ele configuraria seu próprio manipulador de exceção FE, salvando o anterior,

O que você está propondo é pegar uma operação que normalmente não causa o disparo de um manipulador de sinal e configurar o processador em um modo não padrão, onde faz com que um manipulador de sinal seja disparado. É totalmente razoável que o código execute essa operação e espere que não acione um manipulador de sinal.

Em relação ao suporte da MS para isso, eles fornecem fenv.h desde Visual C (++) 2013 ou assim.
Destina-se especificamente a ser útil com setjmp / longjmp

Não consigo entender do que você está falando aqui. Afaict, a funcionalidade padrão no fenv.h é útil para implementar a funcionalidade no estilo numpy, e o MS segue o padrão. Não vejo nenhuma função que possa ser usada com setjmp / longjmp.

o código do usuário pode ser executado rápido e solto com sinalizadores FP, então esses avisos devem ser opcionais no máximo.

Limpar cuidadosamente uma bandeira definida por um cálculo intermediário é exatamente o oposto de jogar rápido e solto com eles. Além disso, os avisos são opcionais.

uma forma de chegar ao fundo do lugar de onde vêm é fundamental para que sejam úteis; (melhorado) fpectl é uma maneira rápida de alcançar o último.

Você é literalmente a primeira pessoa em algo como uma década a precisar do SIGFPE para depurar esse tipo de problema e, olhando novamente para os comentários do bug do sábio, parece que você não conseguiu realmente fazer o fpectl funcionar? Não deve causar um despejo de memória. (Parece que o cysignals está substituindo o código do fpectl para que ele nem mesmo seja executado.)

Se isso acontecer novamente, o que você precisa fazer é fazer uma chamada C para habilitar o SIGFPE e, em seguida, usar um depurador para obter um rastreamento de pilha. Você não precisa de um build de depuração para obter um rastreamento de pilha; tudo que você precisa fazer é não remover os símbolos. E ei, agora sabemos caso isso volte a acontecer.

Eu entendo que foi realmente frustrante depurar, mas não é útil insistir que outros projetos precisam mudar ou manter a infraestrutura básica quando você não consegue nem explicar claramente o que isso vai realizar. (Na verdade, não tenho ideia de como você acha que mudar algo estúpido aqui o ajudaria a encontrar esse tipo de bug mais rápido - a ideia de longjmp é destruir as informações mais precisas que você diz que deseja.)

Finalmente, acontece que tudo se resume a um bug de longa data no compilador C Clang. Basicamente, em um determinado intervalo de double uma atribuição como em

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

aumenta FE_INVALID (e às vezes FE_INEXACT ); btw isso também está afetando outros tipos de dados flutuantes. Ótimo MPFR (o MPFR tem que copiar duplicatas em seus flutuadores de precisão arbitrária, certamente), as pessoas forneceram uma solução alternativa, consertando isso para o sage.

Incidentalmente, um bug 8100 relacionado ao clang ainda mais antigo (desde 2010, com uma dúzia de duplicatas fechadas) diz que não há esperança de usar fenv.h do clang para fazer fpectl funcionando corretamente no momento . O Clang não é totalmente compatível com o C99 nesse aspecto e está sendo muito tímido quanto a isso.

Não tenho certeza se numpy deseja fazer algo a respeito; talvez uma observação de que RuntimeWarning pode ser meramente devido a um bug do compilador (citando "bug clang 8100", é um exemplo bastante proeminente) pode ser útil.

bug 8100 não é relevante; isso é para os pragmas C99 para desabilitar otimizações de ponto flutuante, e nenhum compilador mainstream as suporta. numpy parece funcionar (principalmente) de qualquer maneira :-)

O espírito do bug 8100 é que o clang não se preocupa com as operações FP sendo compiladas corretamente; embora um advogado possa discordar. :-)

OK, o bug 17686 já mencionado é relevante com certeza.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

inducer picture inducer  ·  3Comentários

dmvianna picture dmvianna  ·  4Comentários

manuels picture manuels  ·  3Comentários

ghost picture ghost  ·  4Comentários

astrofrog picture astrofrog  ·  4Comentários