Design: Precisamos de controle de modo de arredondamento de ponto flutuante persistente

Criado em 11 out. 2020  ·  55Comentários  ·  Fonte: WebAssembly/design

Há alguma história nisso. Tenha paciencia comigo.

As políticas de arredondamento de ponto flutuante típicas são: divisão, arredondamento negativo, arredondamento positivo e arredondamento mais próximo ou par. A Intel tomou a decisão idiota de codificar essa política na palavra de controle de ponto flutuante, o que significa que se você quiser alterá-la com frequência, precisará manipular a palavra de controle (FLDCW) o tempo todo, o que é caro.

A nVidia percebeu que isso era um pesadelo para a aritmética de intervalo, que ganhou destaque para a computação científica nos últimos anos, principalmente porque requer mudanças constantes na política de arredondamento (porque, normalmente, o limite inferior desce enquanto o limite superior sobe após cada instrução de ponto flutuante). Portanto, a nVidia fez as coisas da maneira certa: eles codificaram a política de arredondamento _dentro_ da própria instrução.

Olhando para a sua especificação, parece que arredondar mais próximo ou até mesmo é o único modo compatível na maioria dos casos. Isso torna a matemática de intervalo efetivamente impossível, o que apenas limitará a utilidade do WASM para aplicações científicas.

Permita-nos prefixar ou alterar as instruções de ponto flutuante para especificar o modo de arredondamento. Na Intel / AMD, você pode simplesmente otimizar o prefixo se for o mesmo de antes (e não houver alvos de ramificação intermediários). Se o prefixo nunca for usado, então tudo é compatível com versões anteriores para arredondar mais próximo ou até mesmo, portanto, a palavra de controle nunca precisa ser escrita; isso é facilmente verificado durante a autenticação de validade do código. Mas na nVidia, o prefixo é sugado para um campo de bits na instrução.

Por favor, implemente isso. Isso permitirá mais e mais aplicativos úteis do que você pode imaginar atualmente.

Comentários muito úteis

@penzn Se a única maneira de vender isso é ter 4 sabores de cada instrução FP (um para cada modo de arredondamento), então eu concordaria com isso.

Isso pode ser apenas uma variante, mas usando um parâmetro de modo de arredondamento constante.

Sobre os denormais - o wasm, em última análise, os apóia, então, depois de pensar um pouco, pode estar tudo bem do jeito que está.

Pessoalmente, acho que isso pode ser transformado em uma proposta. O que todo mundo pensa?

Todos 55 comentários

A melhor maneira de ajustar o novo comportamento de arredondamento ao WebAssembly seria como novas instruções (em vez de um prefixo de instrução ou um modo de arredondamento persistente ou algo parecido). Você poderia listar as novas instruções de arredondamento que gostaria de ver?

@tlively Justo o suficiente. Eu também nunca fui um grande fã de prefixos.

Para ser claro, há arredondamento no sentido de "arredondar um flutuante existente para um inteiro" e, em seguida, arredondar no sentido de "deslocar as unidades de último lugar (ULP) de acordo com o resultado infinitamente preciso".

No primeiro caso, podemos sobreviver com os suspeitos de sempre:

uma. Arredondar mais próximo ou par.
b. Arredonde em direção ao infinito negativo.
c. Arredonde em direção ao infinito positivo.
d. Corte em direção a zero (truncar).

No segundo caso, podemos aplicar as mesmas políticas de arredondamento, mas para a ULP em vez da parte inteira. Implicitamente, primeiro de tudo temos que saber os limites máximos de erro no resultado infinitamente correto, porque se eles forem muito grandes, então a ULP ainda é indeterminada. Para operações aritméticas, isso é simples e já é feito corretamente no hardware. Para operações que podem produzir resultados irracionais, primeiro é necessário saber os limites de erro na parte truncada (devido ao truncamento da série). Espera-se que os projetistas da CPU tenham feito isso da maneira certa. Tenho sérias dúvidas sobre isso, mas pode ser correto para fsqrt , que AFAIK é sua única operação. Provavelmente, você nunca poderá suportar outras funções transcendentais de uma forma bem definida e multiplataforma porque os projetistas da CPU provavelmente não acertaram os termos de erro. (Essas funções precisariam ser implementadas pelo programador em termos de suas operações aritméticas existentes. Lento, mas bem definido, plataforma cruzada e à prova de futuro. Muitos núcleos, muitos ciclos livres, sem problemas. A única outra opção seria espero que todos os fornecedores de CPU implementem as mesmas funções transcendentais exatamente da mesma maneira. As coisas ficam realmente complicadas se isso acontecer de ser verdade porque estão todas erradas da mesma maneira, e é por isso que eu acho que a abordagem de emulação de software é um mal necessário.)

Obviamente, o primeiro caso pode ser feito após o fato, pois precisamos apenas descartar a parte fracionária. Mas, no segundo caso, tudo precisa acontecer em uma instrução porque, uma vez que uma instrução é concluída, todos os bits extras de precisão foram esquecidos. Portanto, se você deseja instruções individuais, precisa de coisas como "multiplique e arredonde o ULP em direção ao infinito negativo" e "raiz quadrada e trunque tudo após o ULP". A boa notícia é que, se você fizer assim, vai caber como uma luva com a nVidia, o que provavelmente é mais relevante daqui para frente do que o AMD64. A má notícia é que você terá que empregar alguns truques para evitar tocar na palavra de controle com muita frequência na última. E, a propósito, todas as suas instruções aritméticas existentes seriam agora sufixadas com "e arredondar mais próximo ou par".

Vou fazer uma pausa aqui. estamos na mesma página?

Sim, a filosofia aqui parece boa. Tivemos discussões semelhantes na proposta do SIMD. A próxima parte é certificar-se de que as instruções propostas tenham reduções (razoavelmente) eficientes em arquiteturas relevantes (pelo menos x86 / x86-64 / ARM). Não tenho certeza de como a nvidia se encaixa nessa imagem, no entanto. O WebAssembly normalmente não é executado em GPUs. Também seria bom mencionar que tipo de idiomas ou aplicativos se beneficiariam com essas instruções, a fim de motivar o trabalho com eles.

"O WebAssembly normalmente não é executado em GPUs." - Certamente que sim, em 2029.

Mas permita-me sugerir uma solução potencialmente melhor para o segundo caso. Parece-me que a única aplicação prática para arredondamento ULP é matemática de intervalo, ponto final. Portanto, você pode reduzir a explosão de instruções e melhorar o desempenho fornecendo suporte de intervalo nativo. Portanto, "dividir intervalos flutuantes de 64 bits" torna-se "dividir intervalos flutuantes de 64 bits". Sim, existem muitos casos extremos, mas, ao fazer isso dessa maneira, você resolveria todos esses problemas de uma vez por todas, permitindo que idiomas de nível superior traduzissem os tipos de intervalo diretamente para o WASM. Eu sou totalmente compreensivo com a necessidade de evitar bobagens do CISC, mas os intervalos são tão poderosos e tão universais que acho que vale a pena ser um pouco CISC neste caso.

A outra razão para implementar intervalos nativos é que as linguagens de nível superior não têm, até onde sei, instruções para manipulação de ULP. Em muitas linguagens, isso não significaria nada porque os flutuadores são lixo indefinido de qualquer maneira. Mas é fácil imaginar a adição de macros para suportar funções básicas de intervalo em linguagens populares, que então são filtradas diretamente para a camada WASM. Sem chance de criar bugs super sutis devido à manipulação equivocada do ULP.

Se bem me lembro, um foguete Arianne 5 explodiu no nada devido à imprecisão de ponto flutuante que teria sido evitada com intervalos. E se não me engano, a segurança do Large Hadron Collider também foi comprovada com a análise de intervalo. Mas não se trata apenas de segurança. A computação pode ser acelerada exponencialmente, em alguns casos, usando intervalos, simplesmente porque uma precisão mais baixa é freqüentemente aceitável e a economia de tempo se propaga para muitos níveis da árvore de funções. Isso facilita o raytracing muito mais rápido, por exemplo. A dinâmica de fluidos também se beneficia. (Tudo isso está relacionado abstratamente ao algoritmo Shrinking Bullseye.)

Estou intrigado com seus comentários sobre SIMD, mas vou deixar isso para outro tópico.

Não está claro para mim por que a aritmética de intervalo deve fazer parte do ISA WebAssembly em oposição a uma biblioteca de software que compila as instruções existentes do WebAssembly. As arquiteturas nativas suportam aritmética de intervalo diretamente? Do contrário, provavelmente não há benefício em integrá-lo ao WebAssembly.

Estas são as razões pelas quais os intervalos devem ser suportados no WASM:

  1. É quase impossível suportá-los em linguagens de nível superior devido à falta de primitivas de manipulação ULP. O suporte que existe tende a ser extremamente lento porque o resultado correto ainda pode ser obtido, mas apenas por meios complicados envolvendo fundição de tipo.

  2. WASM é o elo que faltava para dar suporte à computação sob demanda generalizada, precisamente porque é bem definido no nível de bits, portanto, é fácil validar que as saídas de nós não confiáveis ​​são idênticas e, portanto, confiáveis. Sem um comportamento bem definido, acabamos com abordagens que exigem que o programador faça a comparação manual dos resultados de uma forma dependente do código. Simplesmente não escala.

  3. Boa ciência e engenharia precisam de matemática de intervalo, mas ela não está sendo muito explorada porque foi inserida em linguagens de nível superior, em vez de incorporada na parte inferior. A nVidia resolveu esse problema, mas falta um código IR decente para acessá-lo em linguagens de nível superior. WASM poderia nos dar aquele IR ausente.

  4. O futuro da computação distribuída é o WASM, mas apenas se você puder torná-lo escalável. Sei que a maioria das pessoas pensa nisso como uma extensão do navegador apenas para executar páginas da Web mais rápidas, mas pode dominar o mundo da computação distribuída até 2030 se você jogar as cartas da maneira certa. Não consigo enfatizar isso o suficiente. Sem intervalos, obtemos ambigüidade escalonável em vez de clareza escalonável.

Se a única solução aceitável são 4 tipos de arredondamento diferentes de seus primitivos existentes, então vou pegá-la, mas o caminho para menos bugs é apenas implementar intervalos na parte inferior. Isso poderia ser estendido ao SIMD em algum ponto também.

Wasm é geralmente projetado como uma abstração mínima sobre o hardware, então a pressão do design é expor versões (padronizadas) do que está no hardware, e não construir o próximo conceito de nível superior, a menos que seja impossível não fazê-lo. Neste caso, parece que os modos de arredondamento estariam mais próximos do hardware e com a adição mais mínima. É meu entendimento que o projeto de controle do modo de arredondamento no IEEE 754 se destina a esse propósito, e também tem outros propósitos, fora da aritmética de intervalo.

Se você fizer assim, pelo menos o processo de compilação será direto, junto com a extensão para SIMD. Isso também significa que a implementação do intervalo precisará ser feita portando essas primitivas aritméticas com reconhecimento de arredondamento para linguagens de nível superior, o que é mais difícil. Mas tudo bem, qual é o próximo passo?

Se bem entendi, você tem o mesmo problema com linguagens de alto nível para compilação nativa, então não acho que seja um problema específico do Wasm que o Wasm precisa resolver.

@brion Bem, como eu disse acima, estou bastante feliz com a ideia de apenas implementar primitivas aritméticas com reconhecimento de arredondamento no WASM.

O problema geral com a manipulação de ULP, quer você resolva com tais primitivas ou suporte de intervalo completo, é que o WASM provavelmente se tornará um método pelo qual a computação distribuída é facilitada para HLLs. Portanto, na verdade, é o hardware. Você não pode ir diretamente do código de máquina HLL para CPU / GPU e executá-lo em destinos anônimos em todo o mundo. (Imagine tentar executar uma rede de computação distribuída que exige que você precise da GPU nVidia XYZ para até mesmo participar.) Portanto, você precisa primeiro ir para o middleware, do qual a escolha óbvia (e talvez única) é WASM. Portanto, se o programador HLL deseja um recurso que _is_ eficientemente implementável em hardware, mas o WASM não o suporta, então ele precisa fazer emulação sem desempenho.

@KloudKoder , f64.fma ajudar com o desempenho de bibliotecas aritméticas de intervalo pelo menos por f64 ?

@Yaffle Você pode explicar seu raciocínio sobre por que multiplicar-adicionar ajudaria? Por exemplo, como isso aceleraria o processo de adição de 2 intervalos, dado que provavelmente ainda funcionará mais próximo ou par?

ajuda a encontrar o erro da multiplicação de dois números f64 ou divisão de números f64:
soma = a + b; erro = -sum + absmáx (a, b) + absmín (a, b);
produto = a * b; erro = fma (a, b, - produto);
quociente = a / b; erro = -fma (quociente, b, -a);
se você conhece o erro, pode arredondar o valor incrementando (por exemplo, quando o erro> 0 e arredondando para infinito positivo) ou diminuindo (por exemplo, quando o erro <0 e arredondando para infinito negativo) o valor pelo menor valor.
Dessa forma, o intervalo resultante será mais apertado.
Embora, se você não precisa do intervalo mais curto, você pode apenas aumentar / diminuir as duas extremidades ...
Aliás, existe um site com algum benchmark - http://verifiedby.me/kv/rounding/index-e.html# : ~: text = Benchmark Hm ...

@Yaffle Espere um segundo, você está falando sobre fma (que acabei de perceber que não está realmente nas especificações, se a função de pesquisa é para ser acreditada) ou fmax / fmin?

Apenas começando com a adição, temos:

(a, b) + (c, d) = (RoundNegative (a + c), RoundPositive (b + d))

Como isso envolveria uma multiplicação, eu não vejo no momento. E multiplicação-adição leva 3 parâmetros, não 1. Claramente, estou interpretando mal sua notação.

@KloudKoder , fma que é fusão-multiplicação add, atualizei minha notação

@Yaffle Acho que finalmente entendi seu ponto. O que você quer dizer é que as várias instruções FMA da Intel calculam:

a * b + c

com precisão infinita e, em seguida, arredondar o mais próximo ou par. (Eles não arredondam depois de apenas computar o produto.)

Isso significa, por um lado, que:

produto = FMUL (a, b)
erro = FMA (a, b, -produto)

é equivalente a:

erro = NearestOrEven (ab - FMUL (a, b))

que, por definição, está errado em não mais do que metade de um ULP, portanto, você precisa apenas deslocar o resultado em 1 ULP para obter um limite superior do erro.

Esta é uma abordagem válida, mas o ajuste de ULP requer sobrecarga intensiva: você precisa primeiro tratar o resultado como um inteiro, em seguida, adicionar seu ULP (como um 1 sem sinal) e, em seguida, ver se você empacotou no campo de expoente ou talvez criou um NaN etc., em seguida, potencialmente fazer uma reversão e atualizar o resultado para o infinito ou o que for. Existem outras maneiras de fazer isso, mas AFAIK também envolve sobrecarga dolorosa. E observe que eu não exigi o intervalo mais apertado - apenas um "apertado o suficiente" para requisitos práticos de precisão. Então, você tem uma maneira rápida de cutucar? Se não, infelizmente estou de volta ao meu ponto original, que é que os intervalos não têm desempenho sob a ABI atual, em relação ao que é realmente possível no hardware real.

Além disso, observe que escrevi FMA acima, que faz alusão ao conjunto de instruções da Intel. O fma de Webassembly, ao contrário, parece não existir. Você tem um link? Ele não aparece na pesquisa de documentos no site.

Finalmente, em seu link:

"adição / subtração / multiplicação / divisão / sqrt incluindo a especificação do modo de arredondamento para o próprio comando"

É exatamente disso que trata esta proposta. É ótimo ter esse recurso, mas precisamos de uma maneira direta de explorá-lo.

Estou feliz por ter meu pessimismo refutado ...

@KloudKoder ,
https://github.com/mskashi/kv/blob/2ec8bac6216d25b226c09e1740487837a4fc5fd5/kv/rdouble-nohwround.hpp#L97

  • esta é outra variante da função "nudge", parece, ela só faz x + ABS(x) * (2**-53+2**-105) quando abs(x) > 2**-969 , que cobre a maioria dos valores f64 ...

f64.fma é mencionado em https://github.com/WebAssembly/design/blob/master/FutureFeatures.md# : ~: text = fma
mas se você não precisa do "intervalo mais estreito", você pode apenas empurrar 1 ulp em ambas as extremidades ... embora, isso possa ser ruim (?).

de qualquer forma, você está certo, que a sobrecarga pode ser grande

@Yaffle Agora vejo f64.fma em "Operadores de ponto flutuante adicionais". Estou disposto a presumir que será adotado como uma instrução. E, definitivamente, eu não exigiria o intervalo mais apertado, o que provavelmente não oferecerá quaisquer vantagens práticas. Agora, olhando para o seu empurrão alternativo:

x + ABS (x) * (2 -53 + 2 -105)

está claro que o objetivo disso é adicionar o resultado (x) a: ele próprio deslocado para baixo de modo que seu 1 inicial apareça no ULP. Não tenho certeza de por que precisamos de (2 ** (- 105)) - talvez para desfazer o caso de empate de mais próximo ou até em favor de um empurrão diferente de zero - mas, em qualquer caso, aqui está o que gostaríamos necessário fazer:

  1. Dado f64 resultado x.

  2. Se x for +/- 0,0, mova-o para cima / para baixo até o menor número positivo / negativo e saia.

  3. Subtraia 53 do campo do expoente. Chame o resultado de y. (Em algumas arquiteturas, isso pode exigir transporte entre registradores flutuantes e inteiros. Vamos apenas assumir que não há custo para isso.)

  4. Use uma instrução de conjunto condicional (sinalizador para inteiro) para lembrar se # 3 resultou em uma quebra do expoente (geralmente não). Isso envolve várias instruções de inteiros, no entanto, você faz isso, até onde posso ver.

  5. OU o resultado de # 4 para um sinalizador de falha de algo, f, que é extremamente provável que termine como 0.

  6. Seja z, o resultado final, a soma de x e y. Apenas o arredondamento mais próximo ou par garantirá que z é um deslocamento válido de x.

  7. Em algum ponto de verificação futuro antes do commit, verifique se f é 0 e falhe se não for.

Isso de fato funciona. Ele também interrompe gravemente o pipeline, resultando em uma longa série de instruções essencialmente serializadas. Isso poderia ser parcialmente superado entrelaçando todo um conjunto de operações de intervalo que podem ocorrer em paralelo.

Portanto, ainda parece que o suporte a instruções com modos de arredondamento embutidos é muito mais barato. Também se pode simplesmente ignorar a tendência para a aritmética de inteiros e, assim, tornar o WebAssembly menos útil na computação científica. Isso seria uma pena, no entanto.

Obrigado por ajudar a demonstrar o escopo deste problema de forma tão completa. Para onde ir a partir daqui?

E se eu escrever o suporte Intel / AMD para você? Isso ofereceria aos mantenedores motivação suficiente para portá-lo para outras plataformas e torná-lo parte das especificações? Podemos discutir sobre os detalhes específicos e os casos extremos, mas não adianta nem mesmo discuti-los se minha proposta aqui não for suficientemente convincente.

@KloudKoder, obrigado por se oferecer para ajudar na implementação :) Infelizmente, há muito mais do que implementação para padronizar uma nova proposta, então é improvável que isso avance sem que mais da comunidade demonstre interesse. Veja um dos meus comentários anteriores sobre por que recursos interessantes geralmente não são considerados, mesmo que eles não pareçam ter desvantagens técnicas.

@tlively eu li seu comentário anterior e entendo porque novos opcodes são difíceis de vender.

Nesse caso, isso é particularmente irritante porque os intervalos têm sofrido esse problema de adoção do ovo e da galinha desde o dia em que foram propostos pela primeira vez para uso com flutuadores IEEE. Basicamente, há uma grande tentação de evitar usá-los em linguagens de alto nível porque são muito lentos, mas ninguém quer fazer o trabalho sujo de otimizar seu desempenho criando suporte intrínseco porque (surpresa!) Ninguém os acha atraentes para trabalhar com (a menos que estejam desesperados para conter o erro). No final do dia, tornar os intervalos eficientes e trivialmente acessíveis é uma questão de garantir a segurança dos dados - apenas na esfera analógica em vez de digital. Estamos desperdiçando a grande contribuição da nVidia (e talvez de outros) de instruções de ponto flutuante prontas para intervalos.

Um vislumbre de esperança é que já existam especificações públicas sobre o comportamento esperado, disponíveis tanto como um corpo abrangente de axiomas quanto como uma versão de "dieta" para aritmética básica. Portanto, pelo menos alguns aspectos de seu comportamento esperado já estão definidos para nós.

Nesse caso, adicionar aritmética de intervalo ao WebAssembly não ajudaria naquele problema do ovo e da galinha, porque não seria mais rápido do que uma biblioteca aritmética de intervalo compilada para WebAssembly, a menos que haja suporte de hardware subjacente. Eu recomendo seguir o caminho mais fácil de escrever tal biblioteca por enquanto, e se o hardware comum oferecer suporte nativo à aritmética de intervalo no futuro, fará sentido revisitar e adicioná-lo ao WebAssembly também.

@tlively eu entendo isso, mas não é preciso. A nVidia tem primitivas de intervalo de hardware, no sentido de instruções de ponto flutuante com modos de arredondamento embutidos. Eles não podem ser acessados ​​via WebAssembly.

O jeito da Intel envolve definir primeiro uma palavra de controle, o que significa que você precisa fazer um monte de limites altos em série, depois um monte de limites baixos, a fim de minimizar a manipulação da palavra de controle. Escrever uma biblioteca não ajuda porque isso é algo que precisa ocorrer no nível do compilador. Por exemplo, eu poderia criar macros em uma biblioteca que faz operações de intervalo em AVX2 ou X87, mas a menos que eu controle o compilador, não posso fazer com que ele faça essas reordenações para evitar toda a manipulação de palavras de controle incapacitante.

Você pode ver o WebAssembly como uma forma de acelerar sites. Embora eu não discorde, também o vejo como uma plataforma de computação distribuída global à espera de acontecer. O Golem, por exemplo, já o habilitou de alguma forma hacky, mas poderia ser muito mais se garantisse a segurança analógica ao incluir este tipo de suporte. Isso é particularmente importante ao tentar garantir que os resultados obtidos por meio de plataformas diferentes (incluindo aquelas que não usam WebAssembly) sejam consistentes entre si, pois os resultados de ponto flutuante de cálculos complicados raramente são exatamente iguais. A ausência de intervalos significa que isso efetivamente não pode acontecer e o WebAssembly nunca atinge seu verdadeiro potencial. Portanto, há muito mais nisso do que apenas "faça isso rápido".

Ah, entendo, não sabia que isso já era suportado pelo hardware Intel. Quão difundido é o suporte?

É praticamente onipresente em CPUs, não apenas no x86, é para isso que servem as funções de biblioteca fegetround e fesetround . A parte complicada é que o modo de arredondamento é um estado "global" (por thread) que afeta as operações de FP.

@tlively

Ah, entendo, não sabia que isso já era suportado pelo hardware Intel. Quão difundido é o suporte?

@penzn está correto. Esse tipo de suporte de arredondamento está presente na Intel há décadas. Como ele indicou, é um design terrível porque alterar o modo de arredondamento requer um bloqueio de pipeline de ponto flutuante. A nVidia teve a visão de integrar o modo de arredondamento diretamente em suas instruções, portanto, por exemplo, você pode "somar e arredondar em direção ao infinito positivo" ou "multiplicar e cortar em direção a zero".

O que estou defendendo aqui - senão primitivas de intervalo abrangentes - é pelo menos um conjunto de opcodes e / ou prefixos que permitirão acesso eficiente às instruções relevantes da nVidia. Esses opcodes e / ou prefixos podem, então, ser pelo menos um pouco eficientemente mapeados para a Intel agrupando instruções do mesmo tipo de arredondamento em sequências densas, de modo a minimizar a manipulação do modo de arredondamento. No entanto, isso pode ser feito como um aprimoramento de desempenho; a primeira versão poderia apenas alterar os modos de arredondamento literalmente conforme especificado pelo código WASM.

Como ele indicou, é um design terrível porque alterar o modo de arredondamento requer um bloqueio de pipeline de ponto flutuante.

Acho que não indiquei isso;) É a realidade do ambiente CPU FP, embora talvez pudesse ter sido projetado melhor.

Esse tipo de suporte de arredondamento está presente na Intel há décadas.

Se eu entendi as coisas corretamente, ele existe no Arm também, pelo menos as mesmas funções fenv funcionam lá. Como de costume, pode haver discrepâncias entre os dois sobre como os diferentes grupos de instrução interagem com o modo de arredondamento.

Há uma discussão sobre um melhor suporte IEEE 754-2019 em recursos futuros:

Para suportar exceções e modos de arredondamento alternativos, uma opção é definir uma forma alternativa para cada um de add , sub , mul , div , sqrt e fma . Essas formas alternativas teriam operandos extras para o modo de arredondamento, traps mascarados e
sinalizadores antigos e um resultado extra para um novo valor de sinalizadores.

Isso é semelhante à ideia original para "SIMD releaxed" fpenv: https://github.com/WebAssembly/design/issues/1401#issuecomment -789073168.

A desvantagem do suporte ao intervalo de primeira classe é que ele exigiria a otimização do tempo de execução na CPU, embora ainda não tenhamos uma implementação de GPU viável em que ela pudesse ter suporte orgânico. No entanto, a ideia em "Future Features" sofre do mesmo problema.

A vantagem da matemática de intervalo é que ela pode reduzir a contagem de instruções (dois pelo preço de um), mas para ser útil, pode precisar ser de nível relativamente alto. Por exemplo, a biblioteca matemática de intervalo utilizável requer mais operações do que as listadas acima e operações adicionais podem não ser fáceis de implementar sem que também se tornem instruções. Outro problema é o agendamento - para fazer alguns limites baixos ou altos em uma linha, partes de diferentes instruções de intervalo precisariam ser agendadas juntas, o que requer ainda mais otimizações no tempo de execução ou um novo modelo de execução.

@penzn Sim, minhas palavras não eram boas. Eu quis dizer que você mencionou a palavra de controle arquitetura, que na minha opinião é um design terrível. O resultado final é que um aumento modesto da porcentagem na contagem de instruções nativas da Intel devido à alteração do modo de arredondamento resulta em uma explosão de múltiplas dobras no tempo de execução devido aos paralisações do pipeline. Como você claramente entende, a única atenuação parcial para isso seria agrupar as instruções pelo modo de arredondamento na medida do possível.

Por mais que eu fique emocionado em ver o suporte de intervalo completo no WASM, é desnecessário. Os blocos de construção da nVidia seriam suficientes para a praticidade. (Eu me pergunto como o Apple M1 faz isso, nesse caso.) Eu entendo que não há suporte para GPU ainda, mas espero que isso esteja na evolução futura do WASM. Por que você acha que o suporte de intervalo precisaria ser mais de alto nível? Mesmo funções transcendentais (por exemplo, log) podem ser calculadas diretamente a partir da aritmética básica de intervalo.

Quanto à proposta de Future Features, seria de grande ajuda para uma implementação eficiente do intervalo. No entanto, sou contra qualquer coisa que defina sinalizadores de resultado (por exemplo, zero, infinito, NaN, etc.) _a menos_ que essa etapa possa ser ignorada de alguma forma, como escrever o resultado dos sinalizadores em um registro de lixo somente para gravação. O motivo é que adiciona uma grande sobrecarga para avaliar esses sinalizadores, mesmo em um nível de hardware. Suspeito que os pipelines de FPU modernos sabem quando podem pular esta etapa, como quando duas instruções de ponto flutuante ocorrem em uma linha, eliminando a necessidade de definir bits de sinalização com base nos resultados da primeira.

O que você acha que é o conjunto mínimo de operações de intervalo que seriam implementáveis ​​e benéficas na CPU também?

@penzn Eu dificilmente consigo pensar em uma questão mais relevante para este tópico.

Em primeiro lugar, existem duas opções básicas de arquitetura:

  1. Parece a Intel. Em outras palavras, tenha uma instrução "definir modo de arredondamento". (O "modo de arredondamento" não é necessário porque um software competente deve saber o que já definiu. Mas não há problema se as pessoas quiserem.)

  2. Parece a nVidia. Em outras palavras, tenha 4 formas de cada instrução: (a) arredondar mais próximo ou par (que é o que já temos em WASM), (b) cortar (para dentro em direção a zero), (c) arredondar em direção ao infinito negativo, e (d) arredondar em direção ao infinito positivo.

Eu gravitaria fortemente em direção à opção # 1 porque (a) será mais simples otimizar para Intel (embora o desempenho teórico seja idêntico em ambos os casos) e (b) eu não acho que alguém queira um zoológico enorme de animais semelhantes com pequenas diferenças em suas listras.

Portanto, supondo que optemos por essa opção, eu recomendaria fortemente o uso de qualquer instrução de "definir ambiente de ponto flutuante" (SFPE) que afete simultaneamente o mascaramento de exceções. Isso sobrecarregará as mudanças do modo de arredondamento com muitas outras lógicas. Separe essas funções em diferentes instruções. (As instruções SFPE são adequadas para fins de compatibilidade, mas devem ser claramente marcadas como sem desempenho.) Criticamente, nem o SFPE nem o modo de arredondamento de conjunto devem aceitar entradas variáveis. (Imagine ter que perguntar "Qual é o modo de arredondamento?" Antes de cada instrução para saber o que executar na nvidia!)

Então, a questão passa a ser: qual é o conjunto mínimo de instruções que vale a pena discutir aqui? Provavelmente:

  1. Defina o círculo mais próximo ou par.
  2. Defina o corte (redondo para dentro).
  3. Definir infinito negativo redondo.
  4. Definir infinito positivo redondo.
  5. Carregue ULP.
  6. Empurre para dentro.
  7. Empurre para fora.
  8. Obtenha o bit de sinal (ligeiramente diferente de "obter sinal").
  9. Xor bits de sinal, saída para inteiro ou sinalizador (ou talvez apenas "ramificar se os bits de sinal (não) forem diferentes").
  10. Copie o bit de sinal para outro valor de ponto flutuante.

Apenas por uma questão de consistência, podemos querer que os primeiros 4 opcodes acima sejam atribuídos de maneira consistente com os bits RC da Intel na palavra de controle da FPU.

5 assume um valor de ponto flutuante (FP) e, em seguida, retorna o valor de FP de menor magnitude que, quando adicionado à entrada com arredondamento de corte, mudaria seu valor. (Implicitamente, ULP (X) tem o mesmo sinal de X.) Se a entrada era qualquer um dos sinais de zero, então a saída deve ser FLOAT_MIN (o menor desnormal) do mesmo sinal. Se a entrada for um sinal de infinito, a saída deve permanecer inalterada. O ULP surge em todos os lugares na aritmética de intervalo, então há uma justificativa fundamental para esta instrução. Por exemplo, seria útil no caso de ser necessário multiplicar ULP por outro valor de FP para calcular um termo de erro composto, como ocorreria ao calcular de forma eficiente (Y log X) via série de Taylor (truncada). Também é usado o tempo todo para saber quando parar de computar tal série.

6 Diminui a mantissa (sem alterar o sinal). Também diminui o expoente se a mantissa for envolvida. Pode resultar em um valor denormal. Se a entrada for um sinal de FLOAT_MIN, então a saída deve ser zero do mesmo sinal. Se a entrada for um sinal de zero ou infinito, o resultado não deve ser alterado.

7 Incrementa a mantissa (sem alterar o sinal). Também incrementa o expoente se a mantissa quebrar. Se a entrada for um sinal de zero, o resultado será FLOAT_MIN do mesmo sinal. Se a entrada for um sinal de infinito, o resultado será o mesmo. Se a entrada for um sinal de FLOAT_MAX, o resultado será infinito do mesmo sinal.

8 Retorna o bit de sinal, que é importante se houver uma diferença semântica entre zero negativo e positivo, que não seria capturado por uma instrução tradicional "obter sinal de operando FP". Observe que se trata do bit de sinal, que no caso mencionado na verdade difere do sinal (aritmético).

9 é importante para a divisão do intervalo. Por exemplo, o recíproco de [2, 3] é [(1/3), (1/2)] (pelo menos (1/3) e no máximo (1/2)). Mas o recíproco de [-2, 3] é [(1/3), (-1/2)], significando no máximo (-1/2) OU pelo menos (1/3). (Limites invertidos implicam em um intervalo aberto, em que um segmento finito está faltando na linha real.) A razão é que [-2, 3] contém zero. Além disso, ele contém pontos à esquerda e à direita de zero, de modo que o recíproco se aproxima do infinito de qualquer um dos signos. Precisamos saber se os sinais são opostos para que possamos determinar se devemos ou não trocar os limites. Sem esta instrução, é muita ramificação com extração de sinal apenas para a função aritmética básica de divisão.

10 Isso surge o tempo todo, embora bons exemplos me escapem no momento. Sem esta instrução, é um monte de paralisações de pipeline.

Não me importo se as instruções acima são indefinidas para operandos NaN, ou se elas se comportam como se o NaN fosse um valor FP normal. Nenhum ponto em acelerar a matemática quebrada. Obviamente, qualquer operação indefinida potencialmente vazará informações sobre o hardware subjacente e anulará a possibilidade de verificação do resultado por meio de comparação.

Eu gravitaria fortemente em direção à opção # 1 porque (a) será mais simples otimizar para Intel (embora o desempenho teórico seja idêntico em ambos os casos) e (b) eu não acho que alguém queira um zoológico enorme de animais semelhantes com pequenas diferenças em suas listras.

Sou totalmente a favor de definir o modo de arredondamento em vez da matemática de intervalo de primeira classe. Eu também estou pessoalmente bem com o modo de arredondamento persistente, embora eu ache que em Wasm estamos tentando evitar um estado global como este - a alternativa seria adicionar variantes de operações matemáticas com a sinalização de "modo de arredondamento" em vez das instruções 1-4 em seu Lista.

Portanto, supondo que optemos por essa opção, eu recomendaria fortemente o uso de qualquer instrução de "definir ambiente de ponto flutuante" (SFPE) que afete simultaneamente o mascaramento de exceções. Isso sobrecarregará as mudanças do modo de arredondamento com muitas outras lógicas. Separe essas funções em diferentes instruções. (As instruções SFPE são adequadas para fins de compatibilidade, mas devem ser claramente marcadas como sem desempenho.) Criticamente, nem o SFPE nem o modo de arredondamento de conjunto devem aceitar entradas variáveis. (Imagine ter que perguntar "Qual é o modo de arredondamento?" Antes de cada instrução para saber o que executar na nvidia!)

+1 nisso também.

  1. Defina o círculo mais próximo ou par.
  2. Defina o corte (redondo para dentro).
  3. Definir infinito negativo redondo.
  4. Definir infinito positivo redondo.
  5. Carregue ULP.
  6. Empurre para dentro.
  7. Empurre para fora.
  8. Obtenha o bit de sinal (ligeiramente diferente de "obter sinal").
  9. Xor bits de sinal, saída para inteiro ou sinalizador (ou talvez apenas "ramificar se os bits de sinal (não) forem diferentes").
  10. Copie o bit de sinal para outro valor de ponto flutuante.

Isso parece um bom começo, essas são operações padrão. No entanto, os denormais podem ser complicados, uma vez que não são consistentes em todas as plataformas.

@penzn Se a única maneira de vender isso é ter 4 sabores de cada instrução FP (um para cada modo de arredondamento), então eu concordaria com isso. A nVidia ficaria satisfeita (e quem sabe, pode dar algum apoio financeiro).

Estou disposto a esquecer os denormais e apenas substituir todas as referências acima a eles por "os valores FP com o expoente normal mínimo" _ desde que haja uma maneira barata de saber quando os resultados não são confiáveis ​​porque um suposto denormal foi gerado. A razão é que o software de nível superior precisa saber que a verificação do resultado está falhando entre a máquina X e a máquina Y porque elas lidam com os denormais de maneira diferente, e não porque um erro físico ocorreu em uma delas. Do contrário, entraríamos em um loop infinito de verificação ou, pior, concluiríamos que um consenso foi alcançado quando, na verdade, muitas máquinas deram a resposta errada da mesma maneira. Presumo que a maneira de detectar a diferença seria desmascarar a exceção denormal ou deixá-la mascarada, mas inspecionar seu status (presumivelmente pegajoso) antes de emitir o resultado final. No entanto, se isso adiciona muita sobrecarga, talvez tenhamos que repensar isso. (Novamente, para mim, "WASM" significa "bytecode de representação intermediária de computação distribuída global", portanto, não estou preocupado com operações de navegador de thread único neste caso.)

@penzn Se a única maneira de vender isso é ter 4 sabores de cada instrução FP (um para cada modo de arredondamento), então eu concordaria com isso.

Isso pode ser apenas uma variante, mas usando um parâmetro de modo de arredondamento constante.

Sobre os denormais - o wasm, em última análise, os apóia, então, depois de pensar um pouco, pode estar tudo bem do jeito que está.

Pessoalmente, acho que isso pode ser transformado em uma proposta. O que todo mundo pensa?

@penzn "apenas uma variante"? Por "variante", você quer dizer "meios alternativos propostos para o modo de arredondamento de codificação" ou meios _suportados_ redundantes do mesmo?

Enquanto os denormais foram uma decisão de design terrível que explodiu a complexidade a fim de oferecer suporte a uma extensão trivial para o domínio representável, suponho que devemos apoiá-los se: (o suporte de hardware é onipresente e deterministicamente compatível entre os principais fornecedores) ou (detectando sua aparência, mesmo se eles já desapareceram, é trivial e universalmente suportado). Não está claro para mim se o predicado anterior é verdadeiro.

Quanto a uma proposta, obviamente, sou a favor. Eu sugeriria que qualquer aprovação envolvia a revisão de pelo menos um especialista envolvido no IEEE1788 ou IEEE1788.1, os padrões aritméticos de intervalo total e parcial, respectivamente. Em particular, devemos cuidar para que o IEEE1788 seja implementado com eficiência usando as primitivas propostas. (Acredito que seja o caso, mas não tenho certeza.) Não que estejamos propondo implementar qualquer um dos padrões aqui, mas deve ser possível fazer isso com uma sobrecarga toleravelmente modesta.

Falando de especialistas em aritmética de intervalo, este é o vídeo que me fez pensar sobre a importância da segurança analógica em 2007. As questões levantadas no vídeo são ainda mais relevantes hoje porque, francamente, tão pouco progresso foi feito. É hora de mudar isso. O palestrante é G. Bill Walster. Vou direto ao assunto, mas vale a pena assistir ao vídeo completo:

https://www.youtube.com/watch?v=U_Cq_h1vrD0&t=680s

Eu entraria em contato com ele, mas não consigo devido a restrições de segurança da minha parte. Talvez um de vocês possa gentilmente informá-lo sobre este tópico. Ele seria um revisor ideal.

https://www.researchgate.net/profile/G-Walster

Desconsidere meu comentário anterior sobre denormals, acho que podemos abraçá-los totalmente aqui, já que é isso que Wasm faz de maneira geral (consulte também WebAssembly / simd # 2).

As operações que você listou são mapeadas para operações matemáticas padrão, provavelmente deveríamos apenas apoiá-las. Suspeito que as bibliotecas aritméticas de intervalo do lado da CPU se baseiam nisso hoje de qualquer maneira.

@penzn "apenas uma variante"? Por "variante", você quer dizer "meios alternativos propostos para o modo de arredondamento de codificação" ou meios _suportados_ redundantes do mesmo?

Quero dizer novas instruções além das instruções existentes na especificação. Digamos que para a multiplicação precisaremos adicionar uma nova instrução (digamos) mul_round , tomando o parâmetro do modo de arredondamento; em seguida, repita isso para o resto das instruções que dependem do arredondamento. Esta seria a maneira mais neutra em termos de especificações, embora seja obviamente redundante, pois as novas instruções incluiriam suporte para o modo de arredondamento existente. Pessoalmente, vejo valor no modo de arredondamento fixo, pois é isso que acontece com os alvos nativos, embora isso possa ser difícil de vender. Outra opção hipotética é o parâmetro de arredondamento opcional de codificação dentro das instruções existentes, mas (1) não está claro como fazer isso e (2) não acho que tenhamos precedente disso.

Acho que há material suficiente para pelo menos uma discussão de CG, para revisar as operações em https://github.com/WebAssembly/design/issues/1384#issuecomment -810695841 e decidir qual modo de arredondamento de codificação é preferível. @KloudKoder você pode participar de uma das reuniões (veja aquelas CG aqui )? Você precisará criar uma conta de Grupo da Comunidade para isso e discar via Zoom - as reuniões são abertas a pessoas com interesse em participar.

Falando de especialistas em aritmética de intervalo, este é o vídeo que me fez pensar sobre a importância da segurança analógica em 2007. As questões levantadas no vídeo são ainda mais relevantes hoje porque, francamente, tão pouco progresso foi feito. É hora de mudar isso. O palestrante é G. Bill Walster. Vou direto ao assunto, mas vale a pena assistir ao vídeo completo:

https://www.youtube.com/watch?v=U_Cq_h1vrD0&t=680s

Obrigado pelo link - o vídeo é muito informativo! Não tenho certeza de qual é o processo do W3C para solicitar análises externas, podemos abordar isso também.

@penzn OK em

Outra opção hipotética é o parâmetro de arredondamento opcional de codificação dentro das instruções existentes

Que tal isso: em vez de criar 3 novas instruções para cada instrução arredondável existente, apenas criamos 3 prefixos de instrução (arredondamento negativo, arredondamento positivo e corte). Nenhum prefixo indica mais próximo ou par, portanto, é compatível com versões anteriores. Prefixos redundantes, prefixos antes de instruções não arredondáveis, tentativas de definir destinos de retorno ou salto imediatamente após um prefixo ou código que termina em um prefixo solitário, todos serão interrompidos pelo linter durante a compilação. (Estou apenas desconfiado da explosão atual e futura nas definições de instrução implícita em 4 modos de arredondamento diferentes, especialmente quando consideramos a inundação incessante de novos SIMD que parece ser uma dinâmica confiável do setor.) Mas esta não é uma linha vermelha para mim. Se a equipe deseja lidar com vários tipos de instrução em vez de implementar prefixos, então que seja.

Você precisará criar uma conta do Grupo da Comunidade

O link, por favor. O zoom é uma falha de segurança gigante, mas verei o que posso fazer. Eu entendo a proposição de valor, no entanto.

Acabei de perceber que também precisamos:

  1. Nudge positivo.
  2. Nudge negativo.

A razão é que, ao calcular uma série de Taylor para estimar uma função transcendental, frequentemente se encontra uma situação em que o erro devido ao truncamento é conhecido por ser menor do que um ULP em magnitude, mas de sinal desconhecido. Nesses casos, precisamos subtrair um ULP do limite inferior e adicionar um ULP ao limite superior. (Por definição, não precisamos considerar implicações de arredondamento ao adicionar ou subtrair um ULP porque o resultado é exato, embora existam alguns casos extremos, por exemplo, o ULP de zero é épsilon, o menor desnormal.) Isso requer empurrões assinados em vez de zeroward nudges.

Para aqueles que, como eu, odeiam adicionar definições de instrução apenas por uma questão de estética, vou admitir preventivamente que todos os empurrões são _tecnicamente_ desnecessários porque alguém pode simplesmente carregar um ULP (instrução # 5) e, em seguida, adicioná-lo ou subtraí-lo de alguns outro flutuador. Mas isso requer o uso explícito de um precioso registro e várias instruções. Além disso, cutucadas são tão fundamentais que acredito que merecem suas próprias instruções. Não me surpreenderia em absoluto vê-los em hardware, já que a indústria de semicondutores finalmente desperta para a relevância dos intervalos na segurança física. Isso agilizaria as coisas e também eliminaria a necessidade de perder tempo e um cadastro de avaliação da ULP.

Observe também que as cutucadas geralmente vêm em pares. Isso significa que, sob o capô, há um "Load ULP" implícito ocorrendo continuamente. Tome cuidado! Este não é necessariamente o mesmo ULP, portanto, apesar das aparências, eles não podem, em geral, ser eliminados.

Link para ingressar no CG: https://www.w3.org/community/webassembly/

Você precisa adicionar um item da agenda (abrir um PR) para uma das reuniões do CG, 4/27 parece ser bastante aberto, também há instruções sobre como obter um convite para a chamada nos arquivos da reunião: https: // github. com / WebAssembly / meeting / tree / master / main / 2021

Em caso de dúvidas, entre em contato. Será um prazer ajudar.

Para aqueles que, como eu, odeiam adicionar definições de instrução apenas por uma questão de estética, vou admitir preventivamente que todos os empurrões são _tecnicamente_ desnecessários porque alguém pode simplesmente carregar um ULP (instrução # 5) e, em seguida, adicioná-lo ou subtraí-lo de alguns outro flutuador. Mas isso requer o uso explícito de um precioso registro e várias instruções. Além disso, cutucadas são tão fundamentais que acredito que merecem suas próprias instruções.

Operações separadas permitem flexibilidade adicional em como podem ser implementadas, embora haja outras considerações também, por exemplo, ganhos de desempenho (ou falta deles). De qualquer forma, isso provavelmente pode ser transformado em uma proposta primeiro e, em seguida, podemos organizar a lista de instruções exata.

Obrigado @penzn . Estou esperando uma resposta ao meu pedido de inscrição em CG. Vou postar de volta a esta questão quando tiver uma data de reunião.

@penzn FYI ainda sem resposta ao meu pedido.

Podemos precisar perguntar a @dschuff sobre isso.

@penzn Obrigado, está em andamento agora, aguardando a resolução de um problema técnico com a inscrição. Apresentarei um relatório.

@penzn A pessoa que estava me ajudando parece ter desaparecido. No momento, finalmente tenho uma conta no W3C, mas estou me perguntando como agendar uma reunião. Devo fazer uma edição em um dos arquivos MD aqui, ou o quê?

https://github.com/WebAssembly/meetings/tree/master/main/2021

Deve ser a maneira mais estranha de agendar uma reunião que já vi, mas entendo: estamos lidando com um corpo de padrões e temos que ser dolorosamente formais.

@KloudKoder sim, isso está correto - edite um dos arquivos e envie um PR com as alterações. Você entrou no grupo da comunidade? O link é https://www.w3.org/community/webassembly/join (pode ser encontrado na página CG . Não me lembro se você foi adicionado às reuniões CG automaticamente ou ainda precisa enviar um e-mail para o presidente.

@dtig , há espaço para isso na reunião de 27 de abril ou seria totalmente consumido por discussões sobre camadas e links? Além disso, se você tiver acesso a convites para reuniões, pode dar uma olhada na situação de @KloudKoder .

@penzn Parece que estamos marcados para 11 de maio de 2021, supondo que eu tenha feito essa RP corretamente?

Acho que você precisa atualizar o PR sobre quanto tempo planeja levar para apresentar ou comentar sobre isso, então será oficial: https://github.com/WebAssembly/meetings/pull/756#issuecomment -822616912

Notas para a reunião de CG hoje (nada gravado na pedra):

LOAD ULP (LULP)

Carregue as unidades do último local em um f32 ou f64. (Esta instrução funciona de forma análoga em f32 e f64.) É importante para operações de intervalo cujos limites de erro são uma função do último termo calculado em uma série de Taylor. Assim, por exemplo, pode-se multiplicar esse último termo por ULP e, em seguida, ampliar o intervalo tanto em cada lado. É muito caro computar com instruções lógicas e aparece em todos os lugares nos cálculos de intervalo de rotina.

Existem alguns casos diferentes:

  1. + -0,0 -> + -epsilon (float diferente de zero de menor magnitude).

  2. + -infinito -> + -infinito. Realmente não há escolha quanto a isso porque a próxima opção lógica é NaN, que é pior.

  3. + -pequeno -> + -epsilon em que pequeno é qualquer coisa menor que 1,0x2 ^ (- 125) para f32 ou 1,0x2 ^ (- 1021) para f64.

  4. + -outro -> + -X onde X é o menor float positivo que, quando adicionado a outro sob arredondamento de corte, mudaria seu valor.

CARREGAR MEIO ULP (LHULP)

Existem funções distintas para LULP e LHULP. LULP é usado para calcular limites de erro finais, mas LHULP é usado para testar o termo de Taylor atual para trivialidade. Uma vez que o prazo atual torna-se menor do que metade de um ULP, geralmente se sabe que o erro máximo é um ULP completo ou o produto de um ULP completo e o prazo atual. Ele tem diferentes casos extremos devido à descontinuidade de domínios exponenciais, e é por isso que é muito caro computar a partir do nível de abstração do programador.

  1. + -0,0 -> + -epsilon.

  2. + -infinito -> + -infinito.

  3. + -pequeno -> + -epsilon onde pequeno é qualquer coisa menor que 1,0x2 ^ (- 124) para f32 ou 1,0x2 ^ (- 1020) para f64.

  4. + -outro -> + - (X / 2) onde X é o menor float positivo que, quando adicionado a outro arredondamento de corte, mudaria seu valor.

NUDGE INWARD (NIN)

Esta instrução é usada para expandir um intervalo aberto movendo um de seus limites para dentro em direção a zero por um ULP. (Intervalos abertos estão implícitos em seus limites sendo listados ao contrário. Eles significam "tudo na linha real, exceto para este intervalo".) Observe que [-0,0, -0,0], [+0,0, +0,0] e [-0,0 , +0,0] significa "apenas zero" enquanto [+0,0, -0,0] significa "tudo exceto zero".

  1. + -infinito -> + -infinito.

  2. + outro -> max (+0,0, + outro -LULP (+ outro)) onde (-0,0) <(+ 0,0).

  3. -outro -> min (-0,0, -outro -LULP (-outro)) onde (-0,0) <(+ 0,0).

Como você pode ver, é muito caro.

NUDGE PARA FORA (NOUT)

Esta instrução é usada para expandir (fechar) intervalos movendo um de seus limites para fora de zero por um ULP.

  1. + -infinito -> + -infinito.

  2. + -outro -> + -outro + LULP (+ - outro).

NUDGE POSITIVE (NPOS)

Esta instrução é usada para aumentar um limite de intervalo pelo limite superior de um termo de erro conhecido como positivo, mas menor que o valor absoluto de um ULP. Esta instrução pode parecer trivial, mas não é porque existem maneiras rápidas de adicionar ou subtrair um ULP sem se dar ao trabalho de criar um ULP seu próprio registro.

  1. + -infinito -> + -infinito.

  2. + outro -> + outro + LULP (+ outro).

  3. -outro -> -outro -LULP (-outro).

NUDGE NEGATIVO (NNEG)

Esta instrução faz o oposto do NPOS.

  1. + -infinito -> + -infinito.

  2. + outro -> + outro -LULP (+ outro).

  3. -outro -> -outro + LULP (-outro).

OBTER SINAL BIT (GSB)

Isso é equivalente a retornar o sinal aritmético de um float, exceto que considera (-0,0) como um número negativo. O objetivo disso é ajudar o código de intervalo a descobrir de que maneira ele precisa fazer o ajuste. É discutível se este bit deve ser enviado a um registrador inteiro ou a um flag. Talvez uma versão de ramificação seja tão boa ou melhor.

DEFINIR BIT DE SINAL (SSB)

Copia o bit de sinal de um bit zero de inteiro, ou talvez o sinalizador de transporte.

BITS DE SINAL XOR (XSB)

Xor o bit de sinal de um flutuador para o bit de sinal de outro. Útil para evitar muitos ramos durante a inversão do intervalo, como pode ocorrer durante a divisão do intervalo.

COPY SIGN BIT (CSB)

Copie o bit de sinal de um flutuador para o bit de sinal de outro. (É o mesmo que CopySign? Acho que precisamos de uma instrução para o bit de sinal e outra para o sinal aritmético.)

ROUND PRÓXIMO OU MESMO, CHOP, ROUND NEGATIVE, ROUND POSITIVE

Esses prefixos, sabores de instrução ou parte de uma variável global que descreve a política de arredondamento? Os prefixos fornecem maior compatibilidade com versões anteriores com menos expansão futura de instrução. Precisa discutir.

Posso não conseguir comparecer à reunião de hoje, então só quero divulgar minhas opiniões

  • +1 para a opção "sabor de instrução"

  • prefixos seriam adequados, mas uma lata de vermes para outras partes da linguagem (resistimos a usá-los como soluções no passado)

  • ter estado / variável global de "política de arredondamento" parece que criaria questões / problemas de design desnecessários sobre a composição do módulo, então preferiria uma das outras duas abordagens

Para registro, a apresentação desta edição ao grupo da comunidade está sendo reprogramada, provisoriamente para 8 de junho. Isso se deve a falhas técnicas em cascata de minha parte que me impediram de participar hoje, apesar de uma tentativa corajosa dos anfitriões de ajudar. Eu sei como evitar que isso aconteça na próxima vez.

@penzn @dschuff Tudo isso foi discutido na última reunião do WASM CG. Devo dizer que fiquei muito impressionado com a qualidade do feedback e a capacidade de todos de se manterem no tópico.

O resultado final é que o grupo deseja ver algum tipo de comparação de desempenho em um caso prático. Recebi a tarefa de criar uma comparação exata em alguma linguagem de desempenho (provavelmente C), não necessariamente compilando para WASM. Em particular, sugere-se que o experimento envolva 2 modos de operação otimizados de forma independente: (1) uma implementação envolvendo os 4 modos de arredondamento acima (por meio de macros de manipulação de modo de arredondamento) e (2) o estado atual das coisas com o arredondamento mais próximo ou uniforme, usando todos os hacks necessários para produzir resultados binários idênticos.

Existem três níveis de dificuldade:

  1. Fácil: Avalie funções transcendentais onde sabemos o sinal e a faixa plausível de saídas com base em uma rápida inspeção do parâmetro. Podemos, portanto, otimizar para uma comutação mínima entre os modos de arredondamento. Isso geralmente surge em simulações de física.

  2. Médio: Produtos de matrizes aleatórias onde todos os valores são positivos, por isso não precisamos inspecionar os sinais para saber arredondar. Isso pode surgir na análise de portfólio, onde as alocações de ativos e os preços são positivos; ou gerenciamento de energia do datacenter, onde a energia e o custo por watt também são positivos.

  3. Difícil: produtos da mesma matriz, mas tendenciosos para ter uma média zero, de modo que os sinais devem ser inspecionados em tempo real. Isso força o thrashing do modo de arredondamento constante ou uma abordagem de 2 passagens que processa intermediários positivos separadamente dos negativos, o que exacerba os acessos à memória. Isso pode surgir na IA, onde temos recompensas e punições.

Eu diria que é garantido que quanto mais difícil for o problema nessa escala, menor será a vantagem de aprimoramentos de intervalo, simplesmente porque há muito mais sobrecarga envolvida na inspeção de sinal por operação. "Difícil" provavelmente viria com uma aceleração marginal, mas os outros são uma questão em aberto.

Também é importante notar que, à medida que a sobrecarga da VM se aproxima do infinito, a taxa de desempenho se aproxima da taxa de contagem de instruções. Talvez eu também pudesse adicionar sobrecarga de VM falsa para que possamos ver como essa transição ocorre à medida que piora.

Vou trabalhar em tudo isso em meu tempo livre e relatar quando tiver um loop de teste para compartilhar.

Pensando em voz alta: parece-me que poderíamos esquecer o ULP (e sua manipulação interna, que é muito cara) se o seguinte tivesse mais desempenho:

Ao adicionar / subtrair intervalos, basta multiplicar o limite superior por (1 + delta) e o limite inferior por (1-delta), onde delta é o ULP de 1,0 e, portanto, constante. Em seguida, faça o oposto. Classifique os resultados (o que deve ser possível em uma única instrução SIMD de 4 vias). Considere o mais negativo como o novo limite inferior e o mais positivo como o novo limite superior. Ou não classifique e apenas pegue o mínimo e o máximo em instruções sucessivas. (Então, novamente, talvez seja melhor usar esse poder SIMD para fazer a construção ULP em 4 flutuadores de uma vez.)

Para multiplicação, calcule todos os 4 produtos possíveis e, a seguir, todas as 8 escalas delta possíveis. Classifique-os e faça o mesmo que acima.

Para a divisão, é basicamente o mesmo, a menos que qualquer intervalo seja igual a zero, então terminamos com intervalos abertos (em oposição aos fechados), e isso é o inferno, não importa o que aconteça.

@ conrad-watt @penzn @tlively

Desculpe por ter demorado tanto. Permita-me compartilhar um pouco do meu pensamento recente. Deixar:

E = épsilon (menor positivo)
S = pequeno (anteriormente "delta": algo como (2 ^ (- 20)) para f32 ou (2 ^ (- 50)) para f64)

Agora você precisa multiplicar [A, B] por [C, D] (a divisão é ainda mais complicada):

  1. Use SIMD para calcular AC, AD, BC e BD.

  2. Use o SIMD para encontrar o X mínimo (mais negativo) e o Y máximo desses 4 valores.

  3. Use SIMD para calcular (X (1-S)), (X (1 + S)), (Y (1-S)) e (Y (1 + S)).

  4. Use o SIMD para encontrar o mínimo P e o máximo Q desses 4 valores.

  5. Emitir produto [P, Q].

Como sugeri anteriormente, a ideia aqui é que você escolha S grande o suficiente para superar o "arrasto" incorreto induzido por mais próximo ou até em todos os casos, portanto, você não precisa de nenhum modo de arredondamento personalizado. Gênio, certo?

Infelizmente ... não funciona! Considere o caso em que, digamos, o valor exato de AC é apenas ligeiramente menor do que E. Então, mais próximo-ou-par irá aumentá-lo para exatamente E. Você tenta corrigir isso multiplicando por (1-S), mas o mais próximo ou mesmo o resultado ainda é E devido a um erro de quantização. O mesmo vale para a maioria dos denormais.

Portanto, além do acima, pode-se ter um rastreador contínuo que lembra o menor valor absoluto diferente de zero já gerado. Se cair abaixo de algum limite, então você sabe que o truque (1-S) não funcionou, então você precisa fazer um rollback e computar tudo de novo usando algum algoritmo totalmente diferente. Não. Diversão.

Então, onde isso nos deixa? Pelo que posso ver, ainda precisamos urgentemente de modos de arredondamento personalizados porque sem eles tudo é simplesmente grotesco (manipulação de ULP com extração e comparação de expoentes, manipulação de inteiros de flutuadores durante a verificação de envoltório, verificação de espaço de mantissa suficiente para compensar para arrasto mais próximo ou par, etc.).

Em outras palavras, ainda não consigo ver como construir um teste justo de modos de arredondamento personalizados com e sem em alguns idiomas e tê-lo em qualquer lugar perto do caso "sem", embora ainda seja aproximadamente ideal em ambos casos.

Pensamentos?

Esqueci de mencionar: uma maneira tentadora de sair do problema (1-S) é apenas adicionar ou subtrair algum múltiplo de E do mínimo e máximo e, em seguida, tomar o mínimo e o máximo novamente para lidar com os sinais adequadamente. Se não me engano, isso de fato funciona. No entanto, chegamos a uma situação em que intervalos puramente positivos podem ter produtos que interferem nos negativos. Isso abre uma nova lata de vermes porque geralmente tal comportamento seria considerado impossível, independentemente da precisão. Ele tem efeitos reais de codepath quando, então, potencialmente dá origem a intervalos abertos devido à obtenção de recíprocos (de intervalos fechados que abrangem zero). Poderíamos evitar esse problema, por sua vez, testando limites e lidando manualmente com casos extremos, mas então acho que voltamos ao "grotesco".

Gostaria de receber algum feedback das partes interessadas sobre onde o limite da taxa de desempenho deve estar para justificar a passagem dessas instruções propostas para o padrão. Estou perguntando porque já se passaram meses e ainda não temos nenhuma proposta de ninguém, incluindo eu, que sugira uma relação de desempenho inferior a 2X. Com base no melhor design até agora, esse também parece ser um limite inferior conservador. Implementar um caso de teste parece bastante desafiador porque, dependendo da estratégia em particular, pode envolver a invocação de builtins SIMD específicos para um compilador específico e, em qualquer caso, alguma mágica de ambiente de ponto flutuante. Fazer a emulação de ponto flutuante completo não exigiria nada disso, mas (1) seria um grande projeto por si só e (2) provavelmente não resultaria em uma comparação realista. Minha sensação neste ponto, tendo descido vários buracos de coelho apenas para chegar a implementações alternativas grosseiramente sem desempenho, é que essas instruções propostas e, acima de todos os sabores de modo de arredondamento, oferecem uma vantagem de taxa de desempenho muito maior do que a que foi usada para justificar muitas outras instruções que WASM emprega e tem uma base em hardware. Em outras palavras, realmente precisamos seguir os passos de um grande projeto de demonstração apenas para provar o que agora é, na minha opinião, óbvio? Claro, você pode argumentar que ninguém jamais precisaria da aritmética de intervalo, mas acho que já ultrapassamos esse estágio. A questão em questão é: pode ser feito de forma eficiente, por meio de quaisquer truques criativos, sob WASM como está? E se não, quanta aceleração obrigaria a comunidade a padronizar as instruções em questão? E, finalmente, à luz desse limite, já é óbvio, na ausência de uma demonstração explícita, que iremos excedê-lo? Não estamos em 20% da terra e nunca estivemos.

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

Questões relacionadas

ghost picture ghost  ·  7Comentários

thysultan picture thysultan  ·  4Comentários

arunetm picture arunetm  ·  7Comentários

bobOnGitHub picture bobOnGitHub  ·  6Comentários

jfbastien picture jfbastien  ·  6Comentários