Numpy: Problema do rastreador para suporte BLIS no NumPy

Criado em 2 mar. 2016  ·  97Comentários  ·  Fonte: numpy/numpy

Aqui está um problema geral do rastreador para discussão do suporte BLIS no NumPy. Até agora, discutimos isso em dois threads nominalmente não relacionados:

Portanto, este é um novo problema para consolidar futuras discussões como esta :-)

Algumas questões pendentes para destacar:

CC: @tkelman @ matthew-brett @fgvanzee

15 - Discussion build

Comentários muito úteis

Construir BLIS no Windows com clang.exe visando MSVC ABI certamente é possível. Passei algumas horas e aqui estão as mudanças. O número de mudanças necessárias foi surpreendentemente baixo em comparação com o OpenBLAS.
O log está aqui . Todos os testes BLIS e BLAS são aprovados.

Todos 97 comentários

Vejo que o BLIS foi incluído em site.cfg .
O libFLAME também pode ser suportado?

A descrição em https://www.cs.utexas.edu/%7Eflame/web/libFLAME.html pode precisar de correção, mas a partir disso não está claro para mim se libFLAME realmente implementa a API LAPACK.

@rgommers libflame realmente fornece APIs netlib LAPACK e implementações para tudo o que libflame não fornece nativamente.

FWIW, o BLIS fez avanços significativos desde que esta edição foi aberta. Por exemplo, o BLIS agora implementa a configuração de tempo de execução e sua configuração de tempo de configuração foi reimplementada em termos de infraestrutura de tempo de execução. O BLIS agora oferece drivers de teste BLAS integrados, além de sua suíte de teste mais abrangente. A auto-inicialização da biblioteca também está em vigor, assim como a geração de cabeçalho monolítico (blis.h único em vez de mais de 500 cabeçalhos de desenvolvimento), o que torna o gerenciamento do produto instalado mais fácil. Ele também segue uma convenção de nomenclatura de biblioteca mais padronizada para suas compilações de biblioteca estática e compartilhada, incluindo um soname . Finalmente, seu sistema de construção é muito mais inteligente em relação à verificação de compatibilidade do compilador e do montador. E é isso que consigo pensar de cabeça.

@fgvanzee obrigado. Nesse caso, recebo +1 para adicionar suporte para ele em numpy.distutils .

Estou com muito pouco tempo no momento, então não posso trabalhar nisso provavelmente até setembro, pelo menos. Se outra pessoa quiser resolver isso, deve ser relativamente direto (na mesma linha de gh-7294). Fico feliz em ajudar a solucionar problemas / revisar.

Isso parece um grande progresso. O quanto você diria que está longe de ser uma alternativa viável ao OpenBLAS para numpy / scipy (desempenho e estabilidade)?

(Observe que ainda não temos um scipy-openblas no Windows por conda devido à bagunça do Fortran: https://github.com/conda-forge/scipy-feedstock/blob/master/recipe/ meta.yaml # L14)

Se você quer um BLAS e LAPACK compatível com MSVC ABI, ainda não há opções fáceis e totalmente de código aberto. Embora hoje em dia com o clang-cl e o flang existam, o problema não é a disponibilidade do compilador como costumava ser, agora é a flexibilidade do sistema de construção e a tentativa de usar combinações que os autores de bibliotecas nunca avaliaram ou ofereceram suporte antes. Ref https://github.com/flame/blis/issues/57#issuecomment -284614032

@rgommers Eu diria que o BLIS é atualmente uma alternativa bastante viável ao OpenBLAS. É viável o suficiente que a AMD tenha abandonado o ACML e abraçado totalmente o BLIS como a base de sua nova solução de biblioteca matemática de código aberto. (Temos patrocinadores corporativos e temos sido patrocinados pela National Science Foundation por muitos anos no passado.)

Em termos de desempenho, a caracterização exata dependerá da operação que você está olhando, do tipo de dados de ponto flutuante, do hardware e da faixa de tamanho do problema em que você está interessado. No entanto, de modo geral, o BLIS normalmente atende ou excede o do OpenBLAS desempenho de nível 3 para todos, exceto os menores tamanhos de problema (menos de 300 ou mais). Ele também emprega uma estratégia de paralelização de nível 3 mais flexível do que o OpenBLAS é capaz (devido ao seu design de kernel de montagem monolítico).

Em termos de estabilidade, gostaria de pensar que o BLIS é bastante estável. Tentamos ser muito receptivos aos relatórios de bugs e, graças ao grande interesse da comunidade nos últimos cinco meses, conseguimos identificar e corrigir muitos problemas (principalmente relacionados ao sistema de construção). Isso facilitou a experiência do usuário tanto para usuários finais quanto para gerenciadores de pacotes.

Além disso, tenha em mente que o BLIS forneceu (desde o dia zero) um superconjunto de funcionalidade semelhante ao BLAS, e o fez por meio de duas novas APIs separadas e separadas da camada de compatibilidade BLAS:

  • uma API tipo BLAS digitada explicitamente
  • uma API baseada em objeto digitada implicitamente

Isso não apenas oferece suporte a usuários legados que já possuem software que precisa de ligação BLAS, mas também fornece uma grande caixa de ferramentas para aqueles interessados ​​em construir soluções customizadas de álgebra linear densa do zero - pessoas que podem não sentir nenhuma afinidade particular com a interface BLAS.

Nascido de uma frustração com as várias deficiências nas implementações existentes do BLAS, bem como na própria API do BLAS, o BLIS tem sido meu trabalho de amor desde 2012. Não vai a lugar nenhum e só vai melhorar. :)

Ah, obrigado @tkelman. Cygwin :( :(

Seria interessante ouvir algumas experiências de pessoas usando o numpy compilado com o BLIS no Linux / macOS.

Obrigado pelo contexto @fgvanzee. Será interessante para nós adicionar suporte para libFLAME e experimentar o conjunto de benchmarks SciPy.

@rgommers Re: libflame: Obrigado pelo seu interesse. Esteja ciente de que o libflame pode usar algum TLC; não está em tão boa forma quanto o BLIS. (Não temos tempo / recursos para apoiá-lo como gostaríamos, e quase 100% de nossa atenção nos últimos seis anos tem se concentrado em levar o BLIS a um lugar onde possa se tornar uma alternativa viável e competitiva ao OpenBLAS et al.)

Em algum ponto, uma vez que o BLIS amadurece e nossas avenidas de pesquisa foram exauridas, provavelmente voltaremos nossa atenção para a funcionalidade no nível do libflame / LAPACK (Cholesky, LU, fatorações QR, por exemplo). Isso pode assumir a forma de adição incremental dessas implementações ao BLIS, ou pode envolver um projeto inteiramente novo para eventualmente substituir libflame. Se for o último, ele será projetado para aproveitar as vantagens de APIs de nível inferior no BLIS, evitando assim algumas chamadas de função e sobrecarga de cópia de memória que atualmente é inevitável por meio do BLAS. Este é apenas um dos muitos tópicos que esperamos investigar.

Eu executei o benchmark deste artigo com NumPy 1.15 e BLIS 0.3.2 em um Intel Skylake sem multithreading (eu tive um erro de instrução de hardware com HT):

Dotted two 4096x4096 matrices in 4.29 s.
Dotted two vectors of length 524288 in 0.39 ms.
SVD of a 2048x1024 matrix in 13.60 s.
Cholesky decomposition of a 2048x2048 matrix in 2.21 s.
Eigendecomposition of a 2048x2048 matrix in 67.65 s.

Intel MKL 2018.3:

Dotted two 4096x4096 matrices in 2.09 s.
Dotted two vectors of length 524288 in 0.23 ms.
SVD of a 2048x1024 matrix in 1.11 s.
Cholesky decomposition of a 2048x2048 matrix in 0.19 s.
Eigendecomposition of a 2048x2048 matrix in 7.83 s.

Pontilhada duas matrizes de 4096x4096 em 4,29 s.

@homocomputeris Desculpe, eu nunca ouvi o verbo "ponto" usado para descrever uma operação em duas matrizes antes. Isso é uma multiplicação de matriz?

@fgvanzee Qual é o status do suporte do Windows no BLIS atualmente? Lembro-me de que começá-lo para construir no Windows costumava ser quase sem suporte ...

@fgvanzee e sim, numpy.dot é a forma tradicional de chamar GEMM em Python. (É um nome meio estranho, mas é porque ele lida com vetor-vetor, vetor-matriz, matriz-matriz, todos na mesma API.)

@njsmith O status de suporte "nativo" do Windows permanece praticamente inalterado. Ainda não temos expertise e interesse em fazer esse apoio acontecer, infelizmente. No entanto, desde que o Windows 10 foi lançado, parece que existe um ambiente de compatibilidade "Ubuntu para Windows" ou bash de algum tipo disponível. Esse é provavelmente um caminho muito mais promissor para obter suporte ao Windows. (Mas, novamente, ninguém em nosso grupo desenvolve ou usa o Windows, então ainda nem examinamos essa opção.)

Ok, uma última postagem por agora ...

@homocomputeris para um benchmark como este ajuda muito mostrar algumas bibliotecas conhecidas também, como o OpenBLAS, porque senão não temos ideia de quão rápido é o seu hardware.

@fgvanzee Falando no suporte de passos largos dos nativos, quais restrições você tem nos dias de hoje? Eles têm que ser alinhados, positivos, não negativos, múltiplos exatos do tamanho dos dados, ...? (Como você deve se lembrar, matrizes numpy permitem avanços totalmente arbitrários medidos em bytes.)

@fgvanzee "bash for Windows" é efetivamente equivalente a executar uma VM Linux no Windows - uma VM particularmente rápida e perfeita, mas não é um ambiente nativo. A boa notícia é que você já oferece suporte ao bash para Windows :-), mas a má notícia é que ele não substitui o suporte nativo do Windows.

@njsmith Meus resultados são mais ou menos iguais aos do artigo.
MKL mais recente, por exemplo:

Dotted two 4096x4096 matrices in 2.09 s.
Dotted two vectors of length 524288 in 0.23 ms.
SVD of a 2048x1024 matrix in 1.11 s.
Cholesky decomposition of a 2048x2048 matrix in 0.19 s.
Eigendecomposition of a 2048x2048 matrix in 7.83 s.

Quero observar que não tenho ideia de como compilar o BLIS para usar tudo o que minha CPU pode otimizar e multithread. Enquanto MKL tem coisas mais ou menos fora da caixa.

@njsmith Obrigado por essa atualização. Eu concordo que nada supera o suporte a sistema operacional nativo. Também concordo que precisamos ver o benchmark rodar com outras bibliotecas para interpretarmos corretamente os tempos de @homocomputeris .

Por falar no suporte nativo de passadas, que restrições você tem para as passadas hoje em dia? Eles têm que ser alinhados, positivos, não negativos, múltiplos exatos do tamanho dos dados, ...? (Como você deve se lembrar, matrizes numpy permitem avanços totalmente arbitrários medidos em bytes.)

@njsmith Alinhado? Não. Positivo? Acho que eliminamos essa restrição, mas ela não foi totalmente testada. Múltiplos exatos do tipo de dados? Sim, ainda.

Estou trazendo @devinamatthews para a discussão. Meses atrás eu contei a ele sobre seu pedido de passos de bytes, e ele tinha alguns pontos / dúvidas bons na hora que eu não consigo me lembrar. Devin, você pode se lembrar de suas preocupações sobre isso e, em caso afirmativo, articulá-las a Nathaniel? Obrigado.

@homocomputeris Você se importaria de executar size ? Eu me pergunto se o valor que o autor usou (4096) sendo uma potência de dois é um caso de uso particularmente ruim para o BLIS, e não é particularmente realista para a maioria dos aplicativos. Eu sugiro tentar 4000 (ou 3000 ou 2000) em vez disso.

@homocomputeris E você disse que os resultados do BLIS são single-threaded, enquanto os resultados do MKL são multi-threaded?

FWIW, eu procurei construir o BLIS no Windows há algum tempo. O principal ponto problemático no momento é o sistema de construção. Pode ser possível fazer com que o mingw's make use o clang para produzir um binário compatível com MSVC. Eu nunca consegui correr com o tempo que pude gastar nisso, mas parece possível.

Dentro do código-fonte real, a situação não é tão ruim. Recentemente, eles até fizeram a transição para o uso de macros para seus kernels de montagem, então essa é mais uma barreira ao suporte do Windows eliminada. Consulte https://github.com/flame/blis/issues/220#issuecomment -397842370 e https://github.com/flame/blis/pull/224. Parece que os próprios arquivos de origem estão a mais algumas macros / ifdefs de serem compilados no MSVC, mas essa é minha perspectiva como um estranho. Também não tenho ideia de como fazer com que os makefiles BLIS existentes funcionem com o MSVC.

@insertinterestingnamehere Obrigado por não foi projetado com o suporte do Windows em mente. Além disso, o MSVC ainda é compatível com o C99? Se não, esse é outro obstáculo. (BLIS requer C99.)

Bem, eu dei o exemplo acima apenas para mostrar que o BLIS é comparável a outros, é por isso que não incluí nada mais específico.

Mas como você pergunta: smiley:

  • Processador Intel Core i5-6260U com BIOS mais recente e quaisquer patches para Spectre / Meltdown
  • Linux 4.17.3-1-ARCH
  • tudo é compilado com gcc 8.1.1 20180531
  • NumPy 1.15.0rc1
  • Eu escolhi um primo para as dimensões da matriz

Intel MKL 2018.3 limitado a 2 threads (ou seja, meus núcleos de CPU físicos):

Dotted two 3851x3851 matrices in 1.62 s.
Dotted two vectors of length 492928 in 0.18 ms.
SVD of a 1925x962 matrix in 0.54 s.
Cholesky decomposition of a 1925x1925 matrix in 0.10 s.
Eigendecomposition of a 1925x1925 matrix in 4.38 s.

BLIS 0.3.2 compilado com
CFLAGS+=" -fPIC" ./configure --enable-cblas --enable-threading=openmp --enable-shared x86_64

Dotted two 3851x3851 matrices in 3.82 s.
Dotted two vectors of length 492928 in 0.39 ms.
SVD of a 1925x962 matrix in 12.82 s.
Cholesky decomposition of a 1925x1925 matrix in 2.02 s.
Eigendecomposition of a 1925x1925 matrix in 67.80 s.

Então, parece que o BLIS definitivamente deveria ser suportado pelo NumPy pelo menos em sistemas Unix / POSIX / qualquer que seja, como eu imagino o uso do Windows como 'não mexa nele se funcionar'
A única coisa que não sei é a conexão entre MKL / BLIS e LAPACK / libFLAME. A Intel afirma ter muitas coisas otimizadas além do BLAS, como LAPACK, FFT etc.

@fgvanzee Por que uma potência de 2 é ruim para o BLIS? É bastante comum para métodos de colocação, se alguém quiser o FFT mais rápido.

Para numpy et al., Seria suficiente gerenciar o edifício em mingw / MSYS2 --- é o que fazemos atualmente com openblas no Windows (embora isso seja uma espécie de hack em si). Isso limitará o uso de APIs "tradicionais" que não envolvem a passagem de recursos CRT, mas isso é bom para BLAS / LAPACK.

@fgvanzee boa observação sobre C99. WRT o pré-processador C99, surpreendentemente, até mesmo o pré-processador MSVC 2017 não está totalmente preso a isso. Supostamente, eles estão corrigindo isso no momento (https://docs.microsoft.com/en-us/cpp/visual-cpp-language-conformance#note_D).

@fgvanzee @njsmith Aqui está o que precisaríamos fazer para suportar avanços de bytes arbitrários:

1) Modifique a interface. A coisa mais conveniente que me ocorre é adicionar algo como um sinalizador stride_units na interface do objeto.
2) Refatore todos os internos para usar apenas passos de byte. Isso pode não ser uma má ideia em qualquer caso.
3) Ao embalar, verifique o alinhamento do tipo de dados e, caso contrário, use o kernel de embalagem genérico.
a) O kernel de empacotamento genérico também teria que ser atualizado para usar memcpy . Se pudermos usar um parâmetro de tamanho literal, ele não deve ser horrível.
4) Quando C não está alinhado, use também um microkernel virtual que acesse C usando memcpy .

Isso é apenas para as matrizes de entrada e saída. Se alpha e beta podem ser ponteiros arbitrários, então há mais problemas. Observe que no x86 você pode ler / gravar dados desalinhados perfeitamente, mas outras arquiteturas (especialmente ARM) seriam um problema. O compilador também pode introduzir problemas de alinhamento adicionais durante a autovetorização .

@homocomputeris :

  1. Eu não quis dizer que as potências de dois nunca surgem "na natureza", apenas que eles são muuuuito super-representados nos benchmarks, provavelmente porque nós, humanos orientados para o computador, gostamos de contar em potências de dois. :)
  2. Esses resultados de benchmark são realmente semelhantes. Eu adoraria se a resposta à próxima pergunta fosse "não", mas é possível que você tenha executado acidentalmente os dois benchmarks com MKL (ou BLIS) vinculado?
  3. Eu concordo totalmente que poderes de dois surgem em aplicativos relacionados a FFT. Eu trabalhava com processamento de sinais, então eu entendo. :)
  4. Minha preocupação com o BLIS não indo bem com potências de dois é na verdade uma preocupação não exclusiva do BLIS. No entanto, pode ser que o fenômeno que estamos observando seja mais pronunciado com o BLIS e, portanto, uma "penalidade" líquida para o BLIS em relação a uma solução ridiculamente otimizada como o MKL. A preocupação é a seguinte: quando as matrizes têm dimensão que é uma potência de dois, é provável que sua "dimensão principal" também seja uma potência de dois. (A dimensão principal corresponde à distância da coluna quando a matriz é armazenada na coluna, ou à distância da linha quando armazenada na linha.) Vamos supor por um momento o armazenamento da linha. Quando a dimensão principal é uma potência de dois, a linha do cache em que o elemento (i, j) reside vive no mesmo conjunto de associatividade que a linha do cache em que os elementos (i + 1, j), (i + 2, j) , (i + 3, j) etc live - ou seja, os mesmos elementos das linhas subsequentes. Isso significa que quando a operação gemm atualiza, digamos, um microtile real 6x8 de precisão dupla de C, todas essas 6 linhas são mapeadas para o mesmo conjunto de associatividade no cache L1 e, inevitavelmente, algumas delas são removidas antes de serem reutilizado. Esses chamados erros de conflito aparecerão em nossos gráficos de desempenho como picos ocasionais de desempenho. Pelo que eu sei, não há maneira fácil de contornar esse impacto no desempenho. Já empacotamos / copiamos as matrizes A e B, então isso não as afeta tanto, mas não podemos empacotar a matriz C em alguma dimensão de liderança mais favorável sem ter um grande acerto de cópia de memória. (A cura seria pior do que a doença.) Agora, talvez MKL tenha uma maneira de atenuar isso, talvez mudando para um microkernel de formato diferente que minimiza o número de erros perdidos. Ou talvez não, mas sei que o BLIS não tenta fazer nada para mitigar isso. Esperamos que isto responda sua pergunta.
  5. Você está certo de que MKL é mais do que apenas uma funcionalidade BLAS + LAPACK. No entanto, lembre-se de que MKL é uma solução comercialmente mantida de código fechado. Embora esteja disponível para fins não comerciais "de graça", não há garantia de que a Intel não tornará o MKL indisponível ao público no futuro ou começará a cobrar por ele novamente. Além disso, não é tão bom para nós, cientistas da computação, que queremos entender a implementação, ou ajustar ou modificar a implementação, ou construir nossa pesquisa sobre blocos de construção bem conhecidos. Dito isso, se tudo que você quer fazer é resolver o seu problema e seguir em frente com o seu dia, e você está bem para expressá-lo via BLAS, isso é ótimo. :)

@fgvanzee atualizado. Que pena, por algum motivo ele compilou com MKL, embora eu tenha colocado o BLIS na configuração.
O SVD e o EVD estão implementados no LAPACK?

@homocomputeris Sim, o LAPACK implementa SVD e EVD. E espero que as implementações do MKL sejam extremamente rápidas.

No entanto, EVD é um pouco ambíguo: existe o problema do autovalor generalizado e um EVD Hermitiano (ou simétrico). Também existe um EVD tridiagonal, mas normalmente muito poucas pessoas estão interessadas nisso, exceto para as pessoas que estão implementando EVD Hermitian.

O NumPy deixará de oferecer suporte ao Python 2.7 e 3.4 no final de 2018, o que permitirá o uso de compiladores MSVC mais recentes que são compatíveis com C99, esse é um dos motivos pelos quais estamos fazendo essa mudança antes que o suporte oficial 2.7 acabe. Consulte http://tinyurl.com/yazmczel para obter mais informações sobre o suporte C99 em compiladores MSVC recentes. O suporte não é completo (alguém é completo?) Mas pode ser suficiente. Estaremos movendo o NumPy para C99 em algum momento, pois alguns (Intel) solicitaram isso para o suporte numérico aprimorado.

@charris Obrigado por esta informação, Charles. É provável que o que quer que seja implementado seja suficiente, mas não saberemos com certeza até tentarmos.

Em qualquer caso, o BLIS não precisa vencer a MKL para começar a ter uma adoção generalizada; seu concorrente imediato é o OpenBLAS.

O BLIS não precisa vencer a MKL para começar a ter uma adoção generalizada; seu concorrente imediato é o OpenBLAS.

Não poderia concordar mais. MKL está em uma liga própria.

@njsmith Só por curiosidade: Qual seria o interesse da comunidade numpy em novas APIs / funcionalidades que permitissem executar, digamos, gemm em precisão mista e / ou domínio misto? Ou seja, cada operando pode ser de um tipo de dados diferente, com o cálculo sendo executado em (potencialmente) uma precisão diferente de um ou ambos de A e B?

@fgvanzee Interessado, sim, embora o número potencial de combinações seja confuso. Mas, no momento, fazemos conversões de tipo, o que leva tempo e consome memória. Também brinquei com a ideia de ter rotinas otimizadas para tipos inteiros, embora booleano possa ser o único tipo que não sofreria irreparavelmente com estouro.

E sempre há float16. O pessoal de ML pode estar mais interessado nessas funcionalidades. @GaelVaroquaux Pensamentos?

@charris Sim, o número de casos pode ser assustador. Existem 128 combinações de tipo para gemm , assumindo os quatro tipos de dados de ponto flutuante tradicionais, e esse número exclui qualquer inflação combinatória dos parâmetros de transposição / conjugação, bem como possibilidades de armazenamento de matriz (nesse caso, iria até 55.296).

Implementar totalmente os 128 casos, mesmo por meio de uma implementação de referência de baixo desempenho, seria digno de nota, e fazer isso de uma maneira de alto desempenho que minimizasse a sobrecarga seria muito especial. (E, graças à base baseada em objetos do BLIS, se implementássemos isso, custaria muito pouco em termos de código de objeto adicional.)

Se isso interessa a você, assista ao nosso projeto nos próximos dias / semanas. :)

O garfo BLIS é seguro?

Em qualquer caso, o BLIS não precisa vencer a MKL para começar a ter uma adoção generalizada; seu concorrente imediato é o OpenBLAS.

De fato. Para ser honesto, não consegui fazer o NumPy + BLAS funcionar no meu sistema, mas eles parecem ter um desempenho muito semelhante, a julgar pelo artigo que citei antes.
Provavelmente, o libFLAME pode acelerar as operações LAPACK se vinculado ao NumPy.

Outra questão interessante é testar o fork BLIS / libFLAME da AMD em suas CPUs Zen mais recentes para ver se há alguma melhoria. Torna-se ainda mais interessante à luz dos processadores Intel problemáticos.

No Linux e no Windows, atualmente as rodas oficiais do numpy incluem uma cópia pré-construída do OpenBLAS, então nessas plataformas a maneira mais rápida de obter uma cópia é pip install numpy . (Claro que isso não é totalmente justo porque, por exemplo, essa versão é construída com um compilador diferente daquele usado para construir o BLIS localmente, mas pode dar uma ideia.)

O garfo BLIS é seguro?

@jakirkham Falei com @devinamatthews sobre isso, e ele parece pensar que a resposta é sim. O BLIS gera threads sob demanda (por exemplo, quando gemm é invocado) ao invés de manter um pool de threads como o OpenBLAS faz.

Estamos curiosos: que tipo de modelo de threading o numpy espera / prefere / depende, se houver? Você precisa de um pool de threads para reduzir a sobrecarga no caso de uso de chamar muitos problemas muito pequenos em sucessão? (O encadeamento sob demanda do BLIS não é adequado para esse tipo de uso.)

EDIT: Em uma linha semelhante, você precisa de pthreads, ou você aceita OpenMP?

Obrigado pela informação.

Então está usando pthreads, OpenMP, ambos? FWIW, existem problemas conhecidos com OpenMP do GCC e bifurcação que AFAIK não foram resolvidos (cc @ogrisel ). Ser bifurcado por meio do módulo multiprocessing em Python é uma das estratégias de paralelismo mais comuns, ser capaz de usar threads (de preferência pthreads pelo motivo acima) de maneira segura com bifurcação é muito importante em Python em geral. Embora haja mais opções atualmente com coisas como loky, por exemplo, que usam fork-exec.

É certo que Nathaniel saberia muito mais sobre isso do que eu, mas como eu já comecei a escrever este comentário, o IIUC NumPy não usa threading em si, exceto através de bibliotecas externas (por exemplo, BLAS). Embora o NumPy libere o GIL com frequência, o que permite que o threading em Python seja um tanto eficaz. Coisas como Joblib, Dask, etc. tiram vantagem dessa estratégia.

Quanto aos pools de threads, é interessante que você pergunte sobre isso, pois eu estava apenas traçando o perfil de uma técnica de ML para maximizar o desempenho outro dia que faz uma série de rotinas BLAS em Python com NumPy / SciPy. Usando OpenBLAS e um número razoável de núcleos, esta rotina é capaz de saturar os núcleos para o comprimento da rotina, embora não haja threading explícito usado em Python (incluindo NumPy e SciPy) simplesmente porque OpenBLAS usou um threadpool para o comprimento do rotina. Então, sim, os pools de threads são incrivelmente valiosos.

Em um ponto diferente, o BLIS lida com a detecção de arquitetura dinâmica? Isso é muito importante para construir artefatos que podem ser construídos uma vez e implantados em uma variedade de sistemas diferentes.

@jakirkham Obrigado por seus comentários. Suponho que o custo observado de não ter um pool de threads dependerá do tamanho dos operandos da matriz que você está transmitindo e de qual operação está executando. Presumo que gemm seja uma operação típica que você almeja (corrija se eu estiver errado), mas que tamanho de problema você considera típico? Imagino que o custo do modelo de criação / junção do BLIS se manifestaria se você fizesse repetidamente a multiplicação de matrizes em que A, B e C fossem 40x40, mas talvez não tanto para 400x400 ou maior. (TBH, você provavelmente não deve esperar muita aceleração da paralelização de problemas 40x40 para começar.)

Em um ponto diferente, o BLIS lida com a detecção de arquitetura dinâmica? Isso é muito importante para construir artefatos que podem ser construídos uma vez e implantados em uma variedade de sistemas diferentes.

Sim. Este recurso foi implementado não muito tempo atrás, no segundo semestre de 2017. O BLIS agora pode ter como alvo as chamadas "famílias de configuração", com a subconfiguração específica a ser usada escolhida em tempo de execução por meio de alguma heurística (por exemplo, cpuid instrução). Exemplos de famílias apoiadas são intel64 , amd64 , x86_64 .

@fgvanzee Eu realmente acho que o desempenho em pequenas matrizes é um dos pontos fracos do OpenBLAS, então é um pouco preocupante se o BLIS for mais lento de novo ... As pessoas definitivamente usam numpy em todos os tipos de situações, então realmente valorizamos bibliotecas "simplesmente funciona" sem a necessidade de ajustes para casos específicos. (Eu acho que isso é um pouco diferente da configuração clássica de álgebra linear densa, onde os dois casos mais importantes são o autor do algoritmo executando benchmarks e especialistas executando trabalhos de supercomputador de uma semana com administradores de sistemas especializados.) Por exemplo, as pessoas costumam usar numpy com Matrizes 3x3.

Para alguns casos, lidar com isso de forma adequada pode ser apenas uma questão de perceber que você tem um problema muito pequeno e pular totalmente o threading. Para uma gema 3x3, o ideal é provavelmente ser o mais estúpido possível.

Porém, esse tipo de ajuste (ou mesmo a coisa thread-pool-versus-no-thread-pool) pode ser algo que a comunidade iria pular e fazer se o BLIS começar a ter uma implantação generalizada.

Na verdade, isso me lembra: eu estava conversando com alguém na semana passada que sabia que em sua biblioteca ele queria invocar gemm single-threaded, porque ele estava gerenciando threading em um nível superior, e ele estava frustrado porque as bibliotecas blas padrão eram a única maneira de controlar isso é por meio de configurações globais que são muito fáceis de chamar de dentro de uma biblioteca aleatória. A API nativa do BLIS permite que o usuário controle a configuração do thread em uma base chamada a chamada? Eu estou supondo que sim, porque IIRC você não tem nenhuma configuração global, fora da camada de compatibilidade BLAS?

Na verdade, acho que o desempenho em pequenas matrizes é um dos pontos fracos do OpenBLAS, então é um pouco preocupante se o BLIS for mais lento de novo ... As pessoas definitivamente usam numpy em todos os tipos de situações, então realmente valorizamos bibliotecas que " funcionar "sem a necessidade de ajustes para casos específicos.

Eu entendo querer que "apenas funcione", mas estou tentando ser realista e honesto com você. Se você tem um problema 3x3 e o desempenho é realmente importante para você, provavelmente nem deveria usar o BLIS. Não estou dizendo que o BLIS será executado 10 vezes mais lento do que um loop triplo ingênuo para 3x3, apenas que esse não é o tamanho do problema em que as implementações do BLIS se destacam.

Estou surpreso em saber que você está um tanto insatisfeito com o OpenBLAS para pequenos problemas. Qual é o seu ponto de referência? Será que o desempenho é menor do que para grandes problemas? Alcançar o mais alto desempenho possível para pequenos problemas requer uma estratégia diferente daquela para grandes problemas, e é por isso que a maioria dos projetos tem como alvo um ou outro e, em seguida, trata apenas os casos não direcionados de forma subótima.

Para alguns casos, lidar com isso de forma adequada pode ser apenas uma questão de perceber que você tem um problema muito pequeno e pular totalmente o threading. Para uma gema 3x3, o ideal é provavelmente ser o mais estúpido possível.

Eu concordo que um corte é o ideal. Mas determinar onde esse corte deve ser não é trivial e certamente varia de acordo com a arquitetura (e entre as operações de nível 3). Então é bem possível, mas ainda não foi implementado (ou bem pensado).

Porém, esse tipo de ajuste (ou mesmo a coisa thread-pool-versus-no-thread-pool) pode ser algo que a comunidade iria pular e fazer se o BLIS começar a ter uma implantação generalizada.

Sim.

Na verdade, isso me lembra: eu estava conversando com alguém na semana passada que sabia que em sua biblioteca ele queria invocar gemm single-threaded, porque ele estava gerenciando threading em um nível superior, e ele estava frustrado porque as bibliotecas blas padrão eram a única maneira de controlar isso é por meio de configurações globais que são muito fáceis de chamar de dentro de uma biblioteca aleatória.

Sequencial gemm de um aplicativo multithread é um dos nossos casos de uso favoritos para se pensar. (Lembrete: se gemm sequencial for seu único caso de uso, ele pode simplesmente configurar o BLIS com multithreading desabilitado. Mas vamos supor que ele queira construir uma vez e então decidir o threading mais tarde.)

A API nativa do BLIS permite que o usuário controle a configuração do thread em uma base chamada a chamada? Eu estou supondo que sim, porque IIRC você não tem nenhuma configuração global, fora da camada de compatibilidade BLAS?

Mesmo se você configurar o BLIS com multithreading habilitado, o paralelismo é desabilitado (um thread) por padrão. Portanto, essa é a segunda maneira pela qual ele pode resolver seu problema.

Mas vamos supor que ele deseja alterar o grau de paralelismo em tempo de execução. O paralelismo no BLIS pode ser definido em tempo de execução. No entanto, ele ainda envolve a configuração interna do BLIS e a leitura de variáveis ​​de ambiente por meio de setenv() e getenv() . (Consulte a wiki Multithreading para obter mais detalhes.) Portanto, não tenho certeza se isso é um obstáculo para o seu amigo. Queremos implementar uma API em que o threading possa ser especificado de uma maneira mais programática (que não envolva variáveis ​​de ambiente), mas ainda não chegamos lá. Principalmente é sobre a interface; a infraestrutura está pronta. Parte do problema é que todos nós fomos treinados ao longo dos anos (por exemplo, OMP_NUM_THREADS , etc.) para especificar um número ao paralelizar, e isso é uma simplificação grosseira das informações que o BLIS prefere; há cinco loops em nosso algoritmo gemm , quatro dos quais podem ser paralelizados (cinco no futuro). Podemos adivinhar a partir de um número, mas geralmente não é o ideal, pois depende da topologia do hardware. Portanto, isso é parte do que impede o progresso nessa frente.

@devinamatthews Alguma chance de adicionar TBLIS ao longo da mesma veia?

Se você tem um problema 3x3 e o desempenho é realmente importante para você, provavelmente nem deveria usar o BLIS. Não estou dizendo que o BLIS será executado 10 vezes mais lento do que um loop triplo ingênuo para 3x3, apenas que esse não é o tamanho do problema em que as implementações do BLIS se destacam.

Não estou tão preocupado em obter o desempenho ideal para problemas 3x3 (embora, obviamente, seja bom se conseguirmos!). Mas, para dar um exemplo extremo: se numpy for compilado com BLIS como sua biblioteca de álgebra linear subjacente, e algum usuário escrever a @ b em seu código usando numpy, eu definitivamente espero que ele não termine 10x mais lento do que uma implementação ingênua. Esperar que os usuários reconstruam numpy antes de multiplicar matrizes 3x3 é pedir demais :-). Especialmente porque o mesmo programa que multiplica matrizes 3x3 em um lugar também pode multiplicar matrizes 1000x1000 em outro lugar.

Lembrete: se gemm sequencial for seu único caso de uso, ele pode simplesmente configurar o BLIS com multithreading desativado. Mas vamos supor que ele queira construir uma vez e decidir o threading mais tarde.

Ele está enviando uma biblioteca Python. Ele espera que seus usuários pegem sua biblioteca e a combinem com outras bibliotecas e seu próprio código, todos rodando juntos no mesmo processo. Sua biblioteca usa GEMM, e é provável que algum dos outros códigos que ele não controla - ou mesmo conhece - também queira usar GEMM. Ele deseja ser capaz de controlar o encadeamento de suas chamadas ao GEMM, sem afetar acidentalmente outras chamadas não relacionadas ao GEMM que podem estar ocorrendo no mesmo processo. E, idealmente, ele seria capaz de fazer isso sem ter que enviar sua própria cópia do GEMM, uma vez que isso é muito chato de fazer da maneira certa, e também é um pouco conceitualmente ofensivo que um programa tenha que incluir duas cópias de uma grande biblioteca apenas então você pode obter duas cópias de uma única variável inteira number_of_threads . Isso faz mais sentido?

Eu definitivamente espero que ele não fique 10 vezes mais lento do que uma implementação ingênua.

Pensando bem, não ficaria surpreso se fosse várias vezes mais lento do que ingênuo para problemas extremamente pequenos (3x3), mas o ponto de crossover seria baixo, talvez tão pequeno quanto 16x16. E este é um problema que é fácil de colocar em um band-aid. (Eu gostaria de fazer isso para todas as operações de nível 3).

Ele está enviando uma biblioteca Python. Ele espera que seus usuários pegem sua biblioteca e a combinem com outras bibliotecas e seu próprio código, todos rodando juntos no mesmo processo. Sua biblioteca usa GEMM, e é provável que algum dos outros códigos que ele não controla - ou mesmo conhece - também queira usar GEMM. Ele deseja ser capaz de controlar o encadeamento de suas chamadas ao GEMM, sem afetar acidentalmente outras chamadas não relacionadas ao GEMM que podem estar ocorrendo no mesmo processo. E, idealmente, ele seria capaz de fazer isso sem ter que enviar sua própria cópia do GEMM, uma vez que isso é muito chato de fazer da maneira certa, e também é um pouco conceitualmente ofensivo que um programa tenha que incluir duas cópias de uma grande biblioteca apenas assim você pode obter duas cópias de uma única variável inteira number_of_threads. Isso faz mais sentido?

Sim, isso foi útil - obrigado pelos detalhes. Parece que ele seria um candidato perfeito para nossa API de threading ainda inexistente. (Pessoalmente, acho que a convenção da variável de ambiente é quase uma loucura e expressei meus sentimentos aos meus colaboradores em mais de uma ocasião. Dito isso, é bastante conveniente para uma ampla faixa de usuários / comparadores de HPC, então teremos que vir com uma API de tempo de execução adequada que não impede o uso de variáveis ​​de ambiente para que todos fiquem felizes.)

Você se importaria de dizer a ele que isso está definitivamente em nosso radar? Vou me reunir com Robert. Ele pode estar interessado o suficiente para aprovar meu tempo gasto nisso mais cedo ou mais tarde. (A API de threading está em sua lista de desejos há algum tempo.)

Construir BLIS no Windows com clang.exe visando MSVC ABI certamente é possível. Passei algumas horas e aqui estão as mudanças. O número de mudanças necessárias foi surpreendentemente baixo em comparação com o OpenBLAS.
O log está aqui . Todos os testes BLIS e BLAS são aprovados.

@isuruf Obrigado por

Quanto à solicitação pull, tenho alguns comentários / solicitações (todos muito pequenos), mas vou iniciar uma conversa sobre a própria solicitação pull.

PS: Que bom que você conseguiu descobrir os f2c erros. Importei apenas partes suficientes do libf2c para a fonte do driver de teste BLAS (compilado como libf2c.a ) para que as coisas se vinculassem, embora isso exigisse algum hackeamento nos arquivos de cabeçalho. (Não gosto de manter o código Fortran - a ponto de preferir olhar para F2c'ed C do que Fortran - caso você não saiba.)

Intel MKL 2018.3:

Dotted two 4096x4096 matrices in 2.09 s.
Dotted two vectors of length 524288 in 0.23 ms.
SVD of a 2048x1024 matrix in 1.11 s.
Cholesky decomposition of a 2048x2048 matrix in 0.19 s.
Eigendecomposition of a 2048x2048 matrix in 7.83 s.

Uma palavra sobre o benchmark usado acima. Robert me lembrou que, a menos que saibamos como esse benchmark (ou numpy?) Implementa Cholesky, EVD e SVD, os resultados dessas operações não serão tão significativos. Por exemplo, se o código netlib LAPACK for usado para fatoração de Cholesky, o tamanho do bloco algorítmico estará errado (longe do ideal). Além disso, dependendo de quão numerosos os links para MKL, ele pode distorcer ainda mais as coisas porque MKL tem sua própria implementação de fatoração de Cholesky, além de gemm rápido, então pode nem mesmo ser igual em comparação com MKL.

Eu sei que você pode estar usando isso simplesmente para ter uma ideia geral de desempenho, e tudo bem. Apenas entenda que pode haver demônios se escondendo nos detalhes que não permitem usá-los para nada mais do que comparações muito aproximadas.

Geralmente, numpy adia operações como aquelas para qualquer biblioteca LAPACK ou semelhante a LAPACK disponível. As compilações do MKL quase certamente estão usando o diapasão do LAPACK que a Intel vem dentro do MKL. As compilações do BLIS provavelmente estão usando a referência LAPACK ou algo parecido.

Em certo sentido, isso é justo: se você está tentando escolher uma configuração numpy para fazer um SVD rápido, então é relevante que o MKL tenha uma versão ajustada disponível e o BLIS não. (Eu acredito que OpenBLAS está no meio: eles fornecem uma versão modificada do LAPACK com a biblioteca, mas é muito mais próxima da implementação de referência do que a versão de MKL.) Mas sim, com certeza se você está tentando entender o que é BLIS e por que o Se os resultados forem parecidos, é importante lembrar que há muito mais coisas envolvidas no SVD / etc do que apenas a eficiência GEMM pura.

Em certo sentido, isso é justo:

Eu entendo e concordo. É complicado pelo fato de que algumas pessoas usam benchmarks como o proposto anteriormente para medir o desempenho absoluto em um único hardware (ou seja, para julgar quão boa é uma implementação em relação ao desempenho de pico), e alguns usam para comparar implementações. Acho que os comentários de Robert sobre Cholesky et al. são mais direcionados ao primeiro do que ao segundo.

@njsmith @insertinterestingnamehere @rgommers @charris Graças ao trabalho rápido de Isuru, fomos capazes de refinar e mesclar seu suporte para Windows baseado em appveyor no branch master do BLIS. Dê uma olhada e diga-nos se e em que grau isso aborda o problema do suporte do Windows para numpy.

@fgvanzee Você pode

Você pode fazer um link para o PR?

Claro, aqui está.

Essa configuração inicial é ótima! Pelo menos em teoria deve ser o suficiente para permitir a construção numpy com o BLIS no Windows. Coisas como construir uma dll ou suportar MinGW podem ser adicionadas posteriormente.

Uma coisa a ser observada sobre as dependências usadas neste caso: pthreads-win32 é LGPL. No entanto, IDK o que precisa ser feito sobre isso.

O licenciamento é uma questão difícil. Eu conheço algumas grandes empresas cujas políticas legais tornam difícil o uso de licenças "copyleft" como a LGPL, enquanto as licenças "permissivas" não são um problema. Adicionar código LGPL à base de código ou rodas NumPy / SciPy seria definitivamente um motivo de preocupação, justificadamente ou não.

Não o estamos adicionando à base de código aqui. O transporte de um componente LGPL em rodas não é um problema. Agora estamos enviando uma dll gfortran que é GPL com exceção de tempo de execução. Consulte gh-8689

De fato. Além disso, não ficaria surpreso se essas empresas também preferissem a MKL.

Só precisamos ter certeza de que o componente LGPL não está estaticamente vinculado, provavelmente.

@jakirkham Obrigado por seus comentários. Suponho que o custo observado de não ter um pool de threads dependerá do tamanho dos operandos da matriz que você está transmitindo e de qual operação está executando. Presumo que gemm seja uma operação típica que você almeja (corrija se eu estiver errado), mas qual tamanho de problema você considera típico? Imagino que o custo do modelo de criação / junção do BLIS se manifestaria se você fizesse repetidamente a multiplicação de matrizes em que A, B e C fossem 40x40, mas talvez não tanto para 400x400 ou maior. (TBH, você provavelmente não deve esperar muita aceleração da paralelização de problemas 40x40 para começar.)

Principalmente GEMM e SYRK são típicos. O GEMV surge às vezes, mas geralmente é transformado em uma grande operação GEMM, se possível.

Não é atípico para nós ter pelo menos 1 dimensão que é da ordem de 10 ^ 6 elementos. O outro pode estar em qualquer lugar de 10 ^ 3 a 10 ^ 6. Portanto, isso varia um pouco. Existe algo que precisaríamos fazer para que isso use o threadpool ou isso acontece automaticamente? Além disso, como o threadpool se comporta se o processo for bifurcado?

Em um ponto diferente, o BLIS lida com a detecção de arquitetura dinâmica? Isso é muito importante para construir artefatos que podem ser construídos uma vez e implantados em uma variedade de sistemas diferentes.

Sim. Esse recurso foi implementado não muito tempo atrás, no segundo semestre de 2017. O BLIS agora pode ter como alvo as chamadas "famílias de configuração", com a subconfiguração específica a ser usada escolhida em tempo de execução por meio de alguma heurística (por exemplo, instrução cpuid). Exemplos de famílias com suporte são intel64, amd64, x86_64.

Para prefaciar um pouco, temos computadores antigos usando Nehalem até Broadwell. Talvez algumas máquinas pessoais mais novas tenham o Kaby Lake. Portanto, ser capaz de tirar o máximo proveito da arquitetura que nos é fornecida é importante e, ao mesmo tempo, não travar se estivermos executando em uma máquina antiga usando intrínsecos sem suporte. As subconfigurações do Flame suportam este intervalo? Existem códigos extras embutidos para despachar o kernel apropriado em diferentes arquiteturas? Quão granular isso fica? Há algo que precisamos fazer durante a construção para garantir que esses kernels estejam presentes?

O edifício x86_64 permite que você:

  • Penryn (incl. Nehalem e qualquer outro chip Intel SSSE3 de 64 bits)
  • Sandy Bridge (incl. Ivy Bridge)
  • Haswell (+ Broadwell, Skylake, Kaby Lake e Coffee Lake, embora possa não ser totalmente ideal para os três últimos)
  • Xeon Phi (1ª e 2ª geração, poderíamos fazer a 3ª geração também, se necessário)
  • Skylake SP / X / W (provavelmente estará muito perto do ideal em Cannon Lake também)
  • Bulldozer / Piledriver / Steamroller / Excavator
  • Ryzen / EPYC

Observe que alguns deles requerem um compilador e / ou versão binutils suficientemente novos, mas apenas na máquina de construção.

AFAIK libflame não possui kernels especializados para nenhuma arquitetura, tudo depende do BLIS.

@jakirkham teríamos que adicionar uma implementação de threadpool. Por enquanto, é uma implementação básica de fork-join que deve ser segura para fork.

AFAIK libflame não possui kernels especializados para nenhuma arquitetura, tudo depende do BLIS.

Isso é basicamente correto. Embora o libflame tenha alguns kernels intrínsecos (atualmente apenas SSE) para aplicar as rotações de Givens, esses kernels não pertencem ao libflame e estão lá apenas porque o BLIS não existia no momento em que foram escritos e, portanto, não havia outro lugar para abrigá-los. Eventualmente, esses kernels serão reescritos e atualizados e realocados no BLIS.

As subconfigurações do Flame suportam este intervalo? Existem códigos extras embutidos para despachar o kernel apropriado em diferentes arquiteturas? Quão granular isso fica? Há algo que precisamos fazer durante a construção para garantir que esses kernels estejam presentes?

@jakirkham Não tenho certeza do que você quer dizer com "Flame" neste caso. Temos dois produtos: libflame e BLIS. Embora o libflame precise de atenção e provavelmente de uma reescrita, praticamente toda a minha atenção nos últimos anos tem estado no BLIS.

Se você substituir textualmente "Flame" por "BLIS", a resposta será "sim, principalmente".

Quão granular isso se torna?

Não tenho certeza do que você quer dizer, mas o suporte ao kernel no BLIS não é tão extenso quanto no OpenBLAS. Por exemplo, muitas vezes não otimizamos o domínio complexo trsm . Kernels são utilizados, em alguma combinação, por subconfigurações. As subconfigurações são escolhidas por meio de cpuid . Você obtém exatamente os kernels "registrados" pela sub-configuração. Consulte o wiki de configuração para obter detalhes sobre os parafusos e porcas.

Há algo que precisamos fazer durante a construção para garantir que esses kernels estejam presentes?

Se você precisa de detecção de hardware em tempo de execução, você tem como alvo uma família de configuração (por exemplo, intel64 , x86_64 ) no tempo de configuração em vez de uma subconfiguração específica (ou auto , que seleciona uma sub-configuração específica). É isso aí.

Existe algo que precisaríamos fazer para que isso use o threadpool ou isso acontece automaticamente? Além disso, como o threadpool se comporta se o processo for bifurcado?

Como eu disse anteriormente neste thread, o BLIS não usa um pool de threads. E eu reclamo Devin na segurança do garfo (e ele parece pensar que o garfo não será problema).

Para sua informação, os pacotes BLIS conda estão disponíveis para linux, osx, windows no conda-forge. (Atualmente construindo dev branch, esperando por um lançamento). Os pacotes foram construídos com pthreads habilitado e configuração x86_64.

Ele está enviando uma biblioteca Python. Ele espera que seus usuários pegem sua biblioteca e a combinem com outras bibliotecas e seu próprio código, todos rodando juntos no mesmo processo. Sua biblioteca usa GEMM, e é provável que algum dos outros códigos que ele não controla - ou mesmo conhece - também queira usar GEMM. Ele deseja ser capaz de controlar o encadeamento de suas chamadas ao GEMM, sem afetar acidentalmente outras chamadas não relacionadas ao GEMM que podem estar ocorrendo no mesmo processo.

@njsmith Eu falei com Robert e ele está bem em aumentar a prioridade de trabalhar nisso. (Além disso, em uma breve análise, descobri uma condição de corrida no BLIS que se manifestaria a qualquer momento que dois ou mais threads de aplicativo tentassem usar diferentes graus de paralelismo. A correção dessa condição de corrida acomodará simultaneamente as necessidades relacionadas à API de pessoas como seu amigo.)

@jakirkham Um de nossos contatos na Intel, @jeffhammond , nos informa que as implementações do OpenMP rotineiramente empregam um modelo de pool de threads internamente. Portanto, ele nos desencoraja a implementar pools de threads de maneira redundante no BLIS.

Agora, pode ser o caso, como você sugere, que numpy precise / prefira pthreads, caso em que talvez estejamos de volta a um fork / join real acontecendo sob a API de pthreads no estilo fork / join.

Então está usando pthreads, OpenMP, ambos?

Além disso, percebi que esqueci de responder a essa pergunta. O paralelismo multithread do BLIS é configurável: ele pode usar pthreads ou OpenMP. (No entanto, isso não deve ser confundido com a dependência de tempo de execução incondicional do BLIS em pthreads devido à nossa dependência de pthread_once() , que é usado para inicialização da biblioteca.)

Infelizmente, a menos que as implementações OpenMP (por exemplo, GOMP) geralmente se tornem mais robustas para bifurcação, não é realmente uma opção segura em Python. Particularmente não em algo tão baixo na pilha como NumPy.

Infelizmente, a menos que as implementações OpenMP (por exemplo, GOMP) geralmente se tornem mais robustas para bifurcação, não é realmente uma opção segura em Python. Particularmente não em algo tão baixo na pilha como NumPy.

Justo. Portanto, parece que o Numpy confiaria na opção de configuração --enable-threading=pthreads para multithreading via BLIS.

Quando compilado com pthreads, há alguma API pública para obter algum controle programático para evitar problemas de excesso de inscrições ao fazer paralelismo aninhado com processos Python / pools de threads que chamam numpy?

Mais especificamente, aqui estão os tipos de símbolos públicos que eu procuraria:

https://github.com/tomMoral/loky/pull/135/files#diff -e49a2eb30dd7db1ee9023b8f7306b9deR111

Semelhante ao que é feito em https://github.com/IntelPython/smp para MKL e OpenMP.

Quando compilado com pthreads, há alguma API pública para obter algum controle programático para evitar problemas de excesso de inscrições ao fazer paralelismo aninhado com processos Python / pools de threads que chamam numpy?

@ogrisel Ótima pergunta, Olivier. Existe uma maneira de definir parâmetros de threading em tempo de execução, mas atualmente é feito com semântica global. É subótimo porque (além de ser global em vez de por chamada para gemm ou qualquer outra coisa) ele se alimenta de variáveis ​​de ambiente.

Atualmente, estou trabalhando em um pequeno redesenho do código subjacente que permitirá que alguém use uma das chamadas sub-APIs "especialistas" do BLIS para passar uma estrutura de dados extra para gemm que permitirá que chamador para especificar em uma base por chamada qual deve ser a estratégia de paralelização. A estrutura de dados mencionada permitirá que as pessoas especifiquem um único número de threads e deixem o BLIS fazer automaticamente o melhor para descobrir onde obter paralelismo, ou um nível de paralelismo para cada loop no algoritmo de multiplicação de matriz (como o BLIS prefere).

De qualquer forma, acho que essa nova abordagem atenderá às suas necessidades. Já estou na metade das alterações, então acho que estou apenas uma semana ou mais de enviar o novo recurso para o github. Informe-me se este plano parece algo que funcionará para você e / ou se você tiver alguma outra preocupação ou solicitação sobre este tópico.

PS: Dei uma olhada no link loky que você forneceu. Esses símbolos são todos configurados para uma configuração global de threads. Minha solução proposta não impediria a definição global, mas foi projetada para que essa não seja a única opção. Portanto, alguém que deseja usar no máximo 8 núcleos / threads pode fazer com que um aplicativo gere 2 threads, e cada um deles invoca uma instância de gemm que obtém paralelismo de 4 vias. Ou 3 threads de aplicativo em que dois deles chamam gemm com paralelismo de 2 vias e o terceiro usando paralelismo de 4 vias. (Você entendeu a ideia.)

ele se alimenta por meio de variáveis ​​de ambiente.

O que isso significa? É possível usar ctypes do Python para reconfigurar o tamanho do pool de threads padrão / global do BLIS, uma vez que já tenha sido inicializado em um processo Python específico?

ele se alimenta por meio de variáveis ​​de ambiente.

O que isso significa?

@ogrisel O que quero dizer com isso é que temos uma pequena API no BLIS para definir ou obter o número de threads, semelhante a omp_get_num_threads() e omp_set_num_threads() no OpenMP. No entanto, essas funções de API do BLIS são implementadas como chamadas para getenv() e setenv() . Este é apenas um artefato histórico do fato de que um dos casos de uso originais do BLIS era definir uma ou mais variáveis ​​de ambiente no shell (por exemplo, bash ) e, em seguida, executar um aplicativo vinculado ao BLIS, portanto, no tempo em que era conveniente simplesmente desenvolver essa abordagem de variável de ambiente; nunca pretendeu ser a maneira final e perfeita de especificar o número de threads para paralelismo.

É possível usar ctypes do Python para reconfigurar o tamanho do pool de threads do BLIS, uma vez que ele já foi inicializado em um processo Python específico?

O BLIS não usa explicitamente um pool de threads. Em vez disso, usamos um modelo de criação / junção para extrair paralelismo por meio de pthreads. Mas sim, depois que o BLIS foi inicializado, o aplicativo (ou biblioteca de chamada) pode alterar o número de threads em tempo de execução por meio de uma chamada de função, pelo menos em princípio. (Não sei se isso funcionaria na prática , no entanto, porque não sei como o Python lidaria com o BLIS tentando definir / obter variáveis ​​de ambiente.) Mas, como mencionei anteriormente, assim que concluir algumas modificações, o aplicativo / A biblioteca poderá especificar um esquema de paralelização personalizado por chamada no momento em que a operação de nível 3 for chamada. Isso seria feito primeiro inicializando um pequeno tipo de dados struct e, em seguida, passando esse struct para uma versão "especialista" estendida da API BLIS por gemm , por exemplo.) tornaria as variáveis ​​de ambiente desnecessárias para aqueles que não precisam / querem delas. Espero que isso responda sua pergunta.

Muito obrigado pelos esclarecimentos. A API especialista por chamada é interessante, mas exigiria numpy para manter uma API específica para expô-la aos chamadores de nível Python. Não tenho certeza se queremos isso. Eu acho que para usuários entorpecidos, ter uma maneira de alterar o valor atual do nível de paralelismo global (nível de processo) é o suficiente. Eu, pessoalmente, não me importo se isso for alcançado alterando o valor atual da variável env, desde que essa alteração seja levada em consideração nas chamadas BLAS-3 subsequentes.

@charris float16 pode realmente ser interessante para algumas cargas de trabalho de aprendizado de máquina, pelo menos no momento da previsão, embora eu não tenha experiência pessoal com isso.

Eu acho que para usuários entorpecidos, ter uma maneira de alterar o valor atual do nível de paralelismo global (nível de processo) é o suficiente. Eu, pessoalmente, não me importo se isso for alcançado alterando o valor atual da variável env, desde que essa alteração seja levada em consideração nas chamadas BLAS-3 subsequentes.

Bom saber. Se for esse o caso, então o BLIS está muito mais perto de estar pronto para ser usado por um numpy. (Eu gostaria apenas de fazer essas modificações em andamento primeiro, que também corrigem uma condição de corrida previamente despercebida)

Provavelmente é melhor não depender das variáveis ​​de ambiente sendo verificadas novamente em cada chamada, porque getenv não é muito rápido, então pode fazer sentido removê-lo mais tarde. (Além disso, acho que nem mesmo é garantido que seja thread-safe?) Mas bli_thread_set_num_threads chamadas de API devem funcionar, já que mesmo que BLIS pare de chamar getenv o tempo todo, as chamadas de API pode ser ajustado para continuar trabalhando independentemente.

A longo prazo, acho que faria sentido começar a expor algumas APIs do BLAS além do básico em numpy. Uma das coisas que tornam o BLIS atraente em primeiro lugar é exatamente o fato de ele fornecer recursos que outras bibliotecas BLAS não oferecem, como a capacidade de multiplicar matrizes strided, e há trabalho em andamento para estender as APIs BLAS de várias maneiras .

Não queremos codificar detalhes específicos da biblioteca na API do numpy (por exemplo, não queremos que np.matmul comece a receber argumentos correspondentes aos parâmetros JC, IC, JR e IR do BLIS ), mas pode muito bem fazer sentido fornecer um argumento genérico "quantos threads para esta chamada" que funciona apenas em back-ends que fornecem essa funcionalidade.

Uma coisa que não vi mencionada é a precisão do índice. A maioria das bibliotecas fornecidas pelo sistema parece usar inteiros de 32 bits, o que é uma limitação para alguns aplicativos hoje em dia. Em algum momento, seria bom se todos os índices fossem de 64 bits, o que provavelmente requer que forneçamos a biblioteca. Não sei o que estamos fazendo atualmente em relação ao tamanho do índice. @ matthew-brett Ainda estamos compilando com inteiros de 32 bits?

@charris O tamanho do inteiro no BLIS é configurável no tempo de configuração: 32 ou 64 bits. Além disso, você pode configurar o tamanho do inteiro usado na API BLAS independentemente do tamanho do inteiro interno.

Na verdade, isso me lembra: eu estava conversando com alguém na semana passada que sabia que em sua biblioteca ele queria invocar gemm single-threaded, porque ele estava gerenciando threading em um nível superior, e ele estava frustrado porque as bibliotecas blas padrão eram a única maneira de controlar isso é por meio de configurações globais que são muito fáceis de chamar de dentro de uma biblioteca aleatória.

@njsmith Corrigi a condição de corrida que mencionei anteriormente e também implementei a API de multithreading thread-safe por chamada. Por favor, indique para seu amigo fa08e5e (ou qualquer descendente desse commit). A documentação multithreading também foi atualizada e orienta o leitor em suas escolhas, com exemplos básicos fornecidos. O commit está no branch dev por enquanto, mas espero mesclá-lo com master breve. (Eu já coloquei o código na maioria de seus passos.)

EDITAR: Links atualizados para refletir o menor conserto.

Como uma possível adição aos tipos suportados, que tal long double ? Noto que algumas arquiteturas estão começando a oferecer suporte à precisão quádrupla (ainda no software) e espero que em algum ponto a precisão estendida seja substituída pela precisão quádrupla em intel. Não acho que isso seja urgente imediatamente, mas acho que depois de todos esses anos as coisas estão começando a ficar assim.

@charris Estamos nos estágios iniciais de considerar o suporte para bfloat16 e / ou float16 em particular por causa de seus aplicativos de aprendizado de máquina / IA, mas também estamos cientes da demanda por double double e precisão quádrupla. Precisamos estabelecer algumas bases para que seja viável para toda a estrutura, mas está definitivamente em nosso radar de médio a longo prazo.

@charris De acordo com https://en.wikipedia.org/wiki/Long_double , long double pode significar uma variedade de coisas:

  • Tipo de 80 bits implementado em x87, usando armazenamento 12B ou 16B
  • precisão dupla com MSVC
  • dupla precisão dupla
  • precisão quádrupla
    Como o significado é ambíguo e depende não apenas do hardware, mas do compilador usado, é um desastre total para as bibliotecas, porque a ABI não está bem definida.

De uma perspectiva de desempenho, não vejo nenhuma vantagem em float80 (ou seja, x87 long double) porque não há uma versão SIMD. Se alguém puder escrever uma versão SIMD de double double no BLIS, isso deve funcionar melhor.

A implementação float128 no software é pelo menos uma ordem de magnitude mais lenta do que float64 no hardware. Seria prudente escrever uma nova implementação de float128 que ignore toda a manipulação do FPE e seja acessível ao SIMD. A implementação em libquadmath, embora correta, não merece a atenção de uma implementação BLAS de alto desempenho como o BLIS.

Sim, é um problema. Não acho que a precisão estendida valha o esforço e a necessidade de precisão quádrupla é irregular, o dobro é bom para a maioria das coisas, mas quando você precisa, você precisa. Não estou preocupado com o desempenho, a necessidade não é de velocidade, mas de precisão. Observe que acabamos de estender o suporte a um ARM64 com quad precision long double, uma implementação de software, é claro, mas espero que o hardware venha a seguir e pode ser bom ter algo testado e pronto para funcionar.

A proposta BLAS G2 leva em consideração cálculos duplo-duplo e "reproduzíveis" no BLAS. (Reproduzível aqui significa determinístico entre as implementações, mas IIUC também envolve o uso de valores intermediários de alta precisão.)

Estou ansioso para ver isso avançando!

Só para constar, @njsmith estava se referindo a quem está interessado em controlar o threading a partir do software. Minhas cargas de trabalho são constrangedoramente paralelas no momento da previsão e minhas multiplicações de matrizes são relativamente pequenas. Portanto, prefiro paralelizar unidades maiores de trabalho.

Eu fiz alguns trabalhos há cerca de um ano no empacotamento do Blis para PyPi e adicionando ligações Cython: https://github.com/explosion/cython-blis

Achei o Blis muito fácil de empacotar como uma extensão C como esta. O principal obstáculo para mim foi o suporte do Windows. De memória, eram problemas do C99, mas posso estar me lembrando errado.

A interface Cython que adicionei pode ser interessante. Em particular, estou usando os tipos fundidos de Cython para que haja uma única função nogil que pode ser chamada com uma visão de memória ou um ponteiro bruto, para os tipos float e double. Adicionar mais branches para mais tipos também não é problema. Tipos fundidos são basicamente modelos: eles permitem a execução condicional em tempo de compilação, para zero overhead.

Eu ficaria muito feliz em manter um pacote Blis autônomo, manter as rodas construídas, manter uma interface Cython agradável, etc. Acho que seria muito bom tê-lo como um pacote separado, em vez de algo integrado dentro do numpy. Poderíamos então expor mais da API do Blis, sem sermos limitados pelo que outras bibliotecas BLAS suportam.

@honnibal Desculpe pela demora em responder neste tópico, Matthew.

Obrigado pela sua mensagem. Estamos sempre felizes em ver outras pessoas ficarem animadas com o BLIS. Claro, ficaríamos felizes em aconselhar sempre que necessário, se você decidir se integrar ainda mais ao ecossistema Python (um aplicativo, uma biblioteca, módulo, etc.).

Quanto ao suporte do Windows, verifique o suporte clang / appveyor para a ABI do Windows que @isuruf adicionou recentemente. A última vez que ouvi dele, estava funcionando conforme o esperado, mas não fazemos nenhum desenvolvimento para Windows aqui na UT, então não posso controlar isso sozinho. (Embora Isuru tenha apontado para mim uma vez que eu poderia me inscrever no appveyor de uma maneira semelhante ao Travis CI.)

Além disso, entre em contato se tiver alguma dúvida sobre o uso do threading por chamada. (Eu atualizei nossa documentação de multithreading para cobrir este tópico.)

No BLIS 0.5.2 , temos um documento de desempenho que mostra o

Então, se a comunidade entorpecida está se perguntando como o BLIS se compara às outras soluções BLAS líderes, eu o convido a dar uma olhada rápida!

Muito interessante, obrigado @fgvanzee.

Tive que procurar Epyc - parece que é uma marca baseada na arquitetura Zen (possivelmente atualizada para Zen + em algum momento?). Talvez seja melhor renomear para Zen? Para nossa base de usuários, Ryzen / Threadripper são as marcas mais interessantes, eles podem reconhecer Zen, mas provavelmente não Epyc.

Epyc é o nome da linha de servidores AMD. É o sucessor dos produtos AMD Opteron do passado.

Infelizmente, não há uma maneira única de o BLIS rotular seus alvos arquitetônicos, porque o código depende do vetor ISA (por exemplo, AVX2), da microarquitetura do núcleo da CPU (por exemplo, Ice Lake) e da integração SOC / plataforma (por exemplo, processador Intel Xeon Platinum ) O BLIS usa nomes de código de microarquitetura em alguns casos (por exemplo, Dunnington), mas isso não é melhor para todos.

@fgvanzee Você pode considerar adicionar os aliases que correspondem aos nomes de março / mtune / mcpu do GCC ...

@rgommers A subconfiguração dentro do BLIS que cobre Ryzen e Epyc na verdade já se chama zen , pois captura os dois produtos.

Quanto a se Ryzen / Threadripper ou Epyc são marcas mais interessantes (mesmo para usuários entorpecidos), direi o seguinte: se eu pudesse avaliar apenas um sistema AMD Zen, seria o Epyc de última geração, porque: (a) usa uma microarquitetura semelhante à de Ryzen; (b) me dá o máximo de 64 núcleos físicos (e, como bônus, esses núcleos são organizados em uma configuração um tanto nova, semelhante a NUMA); que (c) coloca estresse máximo no BLIS e nas outras implementações. E foi basicamente isso que fizemos aqui.

Agora, felizmente, não há regra dizendo que eu só posso avaliar um sistema Zen. :) No entanto, existem outros obstáculos, especialmente no que diz respeito ao acesso em primeiro lugar. Não tenho acesso a nenhum sistema Ryzen / Threadripper no momento. Se / quando eu obtiver acesso, terei prazer em repetir os experimentos e publicar os resultados de acordo.

Jeff aponta algumas das armadilhas de nomenclatura que enfrentamos. Geralmente, nomeamos nossas subconfigurações e conjuntos de kernel em termos de microarquitetura, mas ainda há mais nuances. Por exemplo, usamos nossa subconfiguração haswell em Haswell, Broadwell, Skylake, Kaby Lake e Coffee Lake. Isso porque todos eles basicamente compartilham o mesmo vetor ISA, que é basicamente tudo com o que o código do kernel do BLIS se preocupa. Mas esse é um detalhe de implementação com o qual quase nenhum usuário precisa se preocupar. Se você usar ./configure auto , quase sempre obterá a melhor subconfiguração e conjunto de kernel para o seu sistema, sejam eles denominados zen ou haswell ou qualquer outro. Por enquanto, você ainda precisa adotar uma abordagem mais prática quando se trata de escolher o seu esquema de encadeamento de maneira ideal, e é aí que entra a integração SoC / plataforma que Jeff menciona.

@jeffhammond Obrigado pela sua sugestão. Já considerei adicionar esses apelidos no passado. No entanto, não estou convencido de que vale a pena. Ele irá adicionar uma desordem significativa ao registro de configuração, e as pessoas que irão examiná-lo em primeiro lugar provavelmente já sabem sobre nosso esquema de nomenclatura para subconfigurações e conjuntos de kernel e, portanto, não serão confundidos pela ausência de certa revisão de microarquitetura nomes nesse arquivo (ou no diretório config ). Agora, se o BLIS exigiu a identificação manual da subconfiguração, via ./configure haswell por exemplo, então acho que a balança definitivamente inclina a favor de sua proposta. Mas ./configure auto funciona muito bem, então não vejo necessidade no momento. (Se desejar, você pode abrir uma edição sobre este tópico para que possamos iniciar uma discussão mais ampla entre os membros da comunidade. Estou sempre aberto a mudar de ideia se houver demanda suficiente.)

sim, nomear é sempre complicado :) obrigado pelas respostas @fgvanzee e @jeffhammond

# 13132 e # 13158 estão relacionados

A discussão se empolgou um pouco; quais são os problemas restantes que precisam ser resolvidos para oferecer suporte oficial ao BLIS no numpy?

Ingenuamente, tentei executar testes numpy com BLIS do conda-forge (cf https://github.com/numpy/numpy/issues/14180#issuecomment-525292558) e para mim, no Linux, todos os testes foram aprovados (mas talvez eu perdeu algo).

Também tentei executar o conjunto de testes scipy de teste no mesmo ambiente e houve uma série de falhas em scipy.linalg (cf https://github.com/scipy/scipy/issues/10744) no caso de alguém ter comentários sobre este.

Observe que o BLIS de conda-forge usa ReferenceLAPACK (netlib) como a implementação LAPACK que usa BLIS como a implementação BLAS e não libflame .

Sobre a opção BLIS no conda-forge, estou certo de que é de rosca única fora da caixa (ao contrário da opção OpenBLAS)?

Provavelmente é melhor mover as discussões do conda-forge para o conda-forge 🙂

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