Numpy: Problema de rastreador para soporte BLIS en NumPy

Creado en 2 mar. 2016  ·  97Comentarios  ·  Fuente: numpy/numpy

Aquí hay un problema general del rastreador para discutir el soporte de BLIS en NumPy. Hasta ahora hemos tenido algunas discusiones sobre esto en dos hilos nominalmente no relacionados:

Así que este es un tema nuevo para consolidar futuras discusiones como esta :-)

Algunas cuestiones pendientes actualmente a destacar:

CC: @tkelman @ matthew-brett @fgvanzee

15 - Discussion build

Comentario más útil

La construcción de BLIS en Windows con clang.exe dirigido a MSVC ABI es ciertamente posible. Pasé unas horas y aquí están los cambios. El número de cambios necesarios fue sorprendentemente bajo en comparación con OpenBLAS.
El registro está aquí . Todas las pruebas BLIS y BLAS pasan.

Todos 97 comentarios

Veo que BLIS se ha incluido en site.cfg .
¿ Se puede libFLAME ?

La descripción en https://www.cs.utexas.edu/%7Eflame/web/libFLAME.html puede necesitar corregirse, pero a partir de eso no me queda claro que libFLAME realmente implemente la API LAPACK.

@rgommers libflame de hecho proporciona las API de netlib LAPACK e implementaciones para todo lo que libflame no proporciona de forma nativa.

FWIW, BLIS ha logrado avances significativos desde que se abrió este número. Por ejemplo, BLIS ahora implementa la configuración de tiempo de ejecución, y su configuración de tiempo de configuración se ha vuelto a implementar en términos de la infraestructura de tiempo de ejecución. BLIS ahora ofrece controladores de prueba BLAS integrados además de su conjunto de pruebas más completo. La autoinicialización de la biblioteca también está implementada, al igual que la generación de encabezados monolíticos (blis.h único en lugar de más de 500 encabezados de desarrollo), lo que facilita la administración del producto instalado. También sigue una convención de nomenclatura de bibliotecas más estandarizada para sus compilaciones de bibliotecas estáticas y compartidas, que incluye un soname . Finalmente, su sistema de compilación es mucho más inteligente en comparación con la verificación de la compatibilidad del compilador y el ensamblador. Y eso es justo lo que puedo pensar en la parte superior de mi cabeza.

@fgvanzee gracias. En ese caso, soy +1 para agregar soporte en numpy.distutils .

Tengo muy poco tiempo en este momento, por lo que probablemente no pueda trabajar en esto hasta septiembre al menos. Si alguien más quiere abordar esto, debería ser relativamente sencillo (en la misma línea que gh-7294). Feliz de ayudar a solucionar problemas / revisar.

Eso suena como un gran progreso. ¿Qué tan lejos diría que está de ser una alternativa viable a OpenBLAS para numpy / scipy (rendimiento y estabilidad)?

(Tenga en cuenta que todavía no tenemos un scipy-openblas en Windows por conda debido al desastre de Fortran: https://github.com/conda-forge/scipy-feedstock/blob/master/recipe/ meta.yaml # L14)

Si desea un BLAS y LAPACK compatibles con MSVC ABI, todavía no hay opciones fáciles de código abierto. Aunque hoy en día con clang-cl y flang existentes, el problema no es la disponibilidad del compilador como solía ser, ahora es la flexibilidad del sistema de compilación y el intento de usar combinaciones que los autores de bibliotecas nunca han evaluado o admitido antes. Ref https://github.com/flame/blis/issues/57#issuecomment -284614032

@rgommers Diría que BLIS es actualmente una alternativa bastante viable a OpenBLAS. Es lo suficientemente viable que AMD haya abandonado ACML y haya adoptado completamente BLIS como la base de su nueva solución de biblioteca matemática de código abierto. (Tenemos patrocinadores corporativos y hemos sido patrocinados por la National Science Foundation durante muchos años en el pasado).

En cuanto al rendimiento, la caracterización exacta dependerá de la operación que esté viendo, el tipo de datos de punto flotante, el hardware y el rango de tamaño del problema que le interesa. Sin embargo, en términos generales, BLIS generalmente cumple o excede los valores de OpenBLAS. Rendimiento de nivel 3 para todos los problemas excepto los más pequeños (menos de 300 aproximadamente). También emplea una estrategia de paralelización de nivel 3 más flexible de la que OpenBLAS es capaz de hacer (debido a su diseño de núcleo de ensamblaje monolítico).

En cuanto a la estabilidad, me gustaría pensar que BLIS es bastante estable. Intentamos ser muy receptivos a los informes de errores y, gracias al aumento de interés de la comunidad durante los últimos cinco meses, hemos podido identificar y solucionar muchos problemas (principalmente relacionados con el sistema de compilación). Esto ha suavizado la experiencia del usuario tanto para los usuarios finales como para los administradores de paquetes.

Además, tenga en cuenta que BLIS ha proporcionado (desde el día cero) un superconjunto de funcionalidades similares a BLAS, y lo ha hecho a través de dos API novedosas separadas y aparte de la capa de compatibilidad BLAS:

  • una API tipo BLAS explícitamente escrita
  • una API basada en objetos de tipo implícito

Esto no solo es compatible con los usuarios heredados que ya tienen software que necesita vinculación BLAS, sino que proporciona una gran caja de herramientas para aquellos interesados ​​en crear soluciones personalizadas de álgebra lineal densa desde cero, personas que pueden no sentir ninguna afinidad particular hacia la interfaz BLAS.

Nacido de una frustración con las diversas deficiencias tanto en las implementaciones de BLAS existentes como en la propia API de BLAS, BLIS ha sido mi trabajo de amor desde 2012. No va a ninguna parte y solo mejorará. :)

Ah, gracias @tkelman. Cygwin :( :(

Sin embargo, sería interesante escuchar algunas experiencias de personas que usan numpy compilado contra BLIS en Linux / macOS.

Gracias por el contexto @fgvanzee. Será interesante para nosotros agregar soporte para libFLAME y probar el paquete de pruebas SciPy.

@rgommers Re: libflame: Gracias por su interés. Solo tenga en cuenta que libflame podría necesitar algo de TLC; no está en tan buena forma como BLIS. (No tenemos el tiempo / los recursos para respaldarlo como nos gustaría, y casi el 100% de nuestra atención durante los últimos seis años se ha centrado en llevar BLIS a un lugar donde podría convertirse en una alternativa viable y competitiva a OpenBLAS et al.)

En algún momento, una vez que BLIS madure y nuestras vías de investigación se hayan agotado, es probable que volvamos nuestra atención a la funcionalidad de nivel libflame / LAPACK (Cholesky, LU, factorizaciones QR, por ejemplo). Esto puede tomar la forma de agregar gradualmente esas implementaciones a BLIS, o puede involucrar un proyecto completamente nuevo para eventualmente reemplazar libflame. Si es lo último, se diseñará para aprovechar las API de nivel inferior en BLIS, evitando así algunas llamadas de función y sobrecarga de copia de memoria que actualmente es inevitable a través de BLAS. Este es solo uno de los muchos temas que esperamos investigar.

Ejecuté el punto de referencia de este artículo con NumPy 1.15 y BLIS 0.3.2 en un Intel Skylake sin multiproceso (tuve un error de instrucción de hardware con 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.

Dos matrices de 4096x4096 con puntos en 4,29 s.

@homocomputeris Lo siento, nunca antes había escuchado el verbo "punto" usado para describir una operación en dos matrices. ¿Es eso una multiplicación de matrices?

@fgvanzee ¿Cuál es el estado del soporte de Windows en BLIS en estos días? Recuerdo que, para construirlo en Windows, la mayoría de las veces no era compatible ...

@fgvanzee y sí, numpy.dot es la forma tradicional de llamar a GEMM en Python. (Una especie de nombre extraño, pero es porque maneja vector-vector, vector-matrix, matrix-matrix, todo en la misma API).

@njsmith El estado del soporte "nativo" de Windows se

Ok, una última publicación por ahora ...

@homocomputeris para un punto de referencia como este, realmente ayuda mostrar una biblioteca conocida también, como OpenBLAS, porque de lo contrario no tenemos idea de qué tan rápido es su hardware.

@fgvanzee Hablando del soporte de zancadas nativo, ¿qué restricciones tienes en estos días? ¿Tienen que estar alineados, positivos, no negativos, múltiplos exactos del tamaño de los datos, ...? (Como recordará, las matrices numerosas permiten pasos totalmente arbitrarios medidos en bytes).

@fgvanzee "bash para Windows" equivale efectivamente a ejecutar una máquina virtual Linux en Windows, una máquina virtual particularmente rápida y sin problemas, pero no es un entorno nativo. Así que la buena noticia es que ya es compatible con bash para Windows :-), pero la mala noticia es que no sustituye al soporte nativo de Windows.

@njsmith Mis resultados son más o menos los mismos que en el artículo.
Último MKL, por ejemplo:

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.

Quiero señalar que no tengo idea de cómo compilar BLIS para usar todo lo que mi CPU puede optimizar y multiproceso. Mientras que MKL tiene cosas más o menos listas para usar.

@njsmith Gracias por esa actualización. Estoy de acuerdo en que nada supera al soporte del sistema operativo nativo. También estoy de acuerdo en que necesitamos ver la prueba comparativa que se ejecuta con otras bibliotecas para que podamos interpretar correctamente los tiempos de

Hablando del apoyo a zancadas nativo, ¿qué restricciones tienes en estos días? ¿Tienen que estar alineados, positivos, no negativos, múltiplos exactos del tamaño de los datos, ...? (Como recordará, las matrices numerosas permiten pasos totalmente arbitrarios medidos en bytes).

@njsmith ¿Alineado? No. ¿Positivo? Creo que eliminamos esa restricción, pero no se ha probado a fondo. ¿Múltiplos exactos del tipo de datos? Si aún.

Traigo a

@homocomputeris ¿Le importaría volver a ejecutar el punto de referencia con un valor diferente para size ? Me pregunto si el valor que usó el autor (4096) siendo una potencia de dos es un caso de uso particularmente malo para BLIS, y no particularmente realista para la mayoría de las aplicaciones de todos modos. Sugiero probar 4000 (o 3000 o 2000) en su lugar.

@homocomputeris ¿ Y dijiste que los resultados de BLIS son de un solo subproceso, mientras que los resultados de MKL son de varios subprocesos?

FWIW, busqué construir BLIS en Windows hace algún tiempo. El principal problema en este momento es el sistema de construcción. Es posible que la marca de mingw use clang para producir un binario compatible con MSVC. Nunca lo logré con el tiempo que pude dedicarle, pero parece posible.

Dentro del código fuente real, la situación no es tan mala. Recientemente, incluso hicieron la transición al uso de macros para sus núcleos de ensamblaje, por lo que se eliminó una barrera más para el soporte de Windows. Consulte https://github.com/flame/blis/issues/220#issuecomment -397842370 y https://github.com/flame/blis/pull/224. Parece que los archivos fuente en sí mismos están a algunas macros / ifdefs más lejos de la construcción en MSVC, pero esa es mi perspectiva como forastero. Tampoco tengo idea de cómo hacer que los makefiles BLIS existentes funcionen con MSVC.

@insertinterestingnamehere Gracias por participar, Ian. Tiene razón en que los núcleos ensambladores re-macroizados están un paso más cerca de ser compatibles con MSVC. Sin embargo, como señala, nuestro sistema de compilación definitivamente no se diseñó teniendo en cuenta la compatibilidad con Windows. Además, ¿MSVC ya es compatible con C99? Si no, ese es otro obstáculo. (BLIS requiere C99).

Bueno, di el ejemplo anterior solo para mostrar que BLIS es comparable a otros, por eso no he incluido nada más específico.

Pero como preguntas: smiley:

  • Procesador Intel Core i5-6260U con BIOS más reciente y los parches para Spectre / Meltdown
  • Linux 4.17.3-1-ARCH
  • todo está compilado con gcc 8.1.1 20180531
  • NumPy 1.15.0rc1
  • Elegí un primo para las dimensiones de la matriz

Intel MKL 2018.3 limitado a 2 subprocesos (es decir, mis núcleos físicos de CPU):

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 con
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.

Entonces, parece que BLIS definitivamente debería ser compatible con NumPy al menos en sistemas Unix / POSIX / lo que sea, ya que imagino que el caso de uso de Windows es 'no lo toques si funciona'
Lo único que no sé es la conexión entre MKL / BLIS y LAPACK / libFLAME. Intel afirma que tiene muchas cosas optimizadas además de BLAS, como LAPACK, FFT, etc.

@fgvanzee ¿

Para numpy et al., Sería suficiente administrar el edificio en mingw / MSYS2 --- eso es lo que hacemos actualmente con openblas en Windows (aunque esto es una especie de truco en sí mismo). Limitará el uso a las API "tradicionales" que no implican pasar recursos CRT, pero eso está bien para BLAS / LAPACK.

@fgvanzee buen punto sobre C99. WRT el preprocesador C99, sorprendentemente, incluso el preprocesador MSVC 2017 no está completamente al día. Supuestamente, actualmente lo están arreglando (https://docs.microsoft.com/en-us/cpp/visual-cpp-language-conformance#note_D).

@fgvanzee @njsmith Esto es lo que deberíamos hacer para admitir pasos de bytes arbitrarios:

1) Modifique la interfaz. Lo más conveniente que se me ocurre es agregar algo como una bandera stride_units en la interfaz del objeto.
2) Refactorice todos los componentes internos para usar solo pasos de bytes. En cualquier caso, esto puede no ser una mala idea.
3) Al empacar, verifique la alineación del tipo de datos y, si no es así, use el núcleo de empaque genérico.
a) El kernel de empaquetado genérico también debería actualizarse para usar memcpy . Si podemos arreglarlo para usar un parámetro de tamaño literal, entonces no debería apestar horriblemente.
4) Cuando C no está alineado, también use un microkernel virtual que acceda a C usando memcpy .

Esto es solo para las matrices de entrada y salida. Si alpha y beta pueden ser punteros arbitrarios, entonces hay más problemas. Tenga en cuenta que en x86 puede leer / escribir datos no alineados sin problemas, pero otras arquitecturas (especialmente ARM) serían un problema. El compilador también puede introducir problemas de alineación adicionales al auto-vectorizar .

@homocomputeris :

  1. No quise dar a entender que los poderes de dos nunca surgen "en la naturaleza", solo que están muy sobrerrepresentados en los puntos de referencia, probablemente porque a los humanos orientados a las computadoras nos gusta contar en potencias de dos. :)
  2. Esos resultados de referencia son realmente similares. Me encantaría que la respuesta a la siguiente pregunta fuera "no", pero ¿es posible que haya ejecutado accidentalmente ambos puntos de referencia con MKL (o BLIS) vinculado?
  3. Estoy completamente de acuerdo en que los poderes de dos surgen en aplicaciones relacionadas con FFT. Solía ​​trabajar en procesamiento de señales, así que lo entiendo. :)
  4. Mi preocupación de que BLIS no funcione bien con potencias de dos es en realidad una preocupación que no es exclusiva de BLIS. Sin embargo, puede ser que el fenómeno que estamos observando sea más pronunciado con BLIS y, por lo tanto, una "penalización" neta para BLIS en relación con una solución ridículamente optimizada como MKL. La preocupación es la siguiente: cuando las matrices tienen una dimensión que es una potencia de dos, es probable que su "dimensión principal" sea también una potencia de dos. (La dimensión principal corresponde al paso de la columna cuando la matriz se almacena en columnas, o al paso de la fila cuando se almacena en filas). Supongamos por un momento el almacenamiento de filas. Cuando la dimensión principal es una potencia de dos, la línea de caché en la que reside el elemento (i, j) vive en el mismo conjunto de asociatividad que la línea de caché en la que los elementos (i + 1, j), (i + 2, j) , (i + 3, j) etc live - es decir, los mismos elementos de las filas subsiguientes. Esto significa que cuando la operación gemm actualiza, digamos, un microtile 6x8 real de doble precisión de C, esas 6 filas se asignan todas al mismo conjunto de asociatividad en la caché L1, e inevitablemente algunas de ellas son desalojadas antes de ser reutilizado. Estos supuestos errores de conflicto aparecerán en nuestros gráficos de rendimiento como picos ocasionales en el rendimiento. Hasta donde yo sé, no hay una manera fácil de evitar este impacto de rendimiento. Ya empaquetamos / copiamos las matrices A y B, por lo que esto no las afecta tanto, pero no podemos empaquetar la matriz C en una dimensión principal más favorable sin recibir una copia de memoria enorme. (La cura sería peor que la dolencia). Ahora, tal vez MKL tenga una forma de mitigar esto, tal vez cambiando a un microkernel de forma diferente que minimice el número de errores de conflicto. O tal vez no lo hagan, pero que BLIS no intenta hacer nada para mitigar esto. Espero eso conteste tu pregunta.
  5. Tienes razón en que MKL es más que solo la funcionalidad BLAS + LAPACK. Sin embargo, tenga en cuenta que MKL es una solución de código cerrado comercial. Si bien está disponible para fines no comerciales "gratis", no hay garantía de que Intel no hará que MKL no esté disponible para el público en el futuro, o que comience a cobrar por él nuevamente. Además, no es tan bueno para nosotros, los científicos de la computación que queremos comprender la implementación, o ajustar o modificar la implementación, o construir nuestra investigación sobre bloques de construcción bien entendidos. Dicho esto, si todo lo que quieres hacer es resolver tu problema y seguir adelante con tu día, y puedes expresarlo a través de BLAS, es genial. :)

@fgvanzee Actualizado. Mi mal, por alguna razón se compiló con MKL, aunque había puesto BLIS en config.
¿Se implementan SVD y EVD en LAPACK?

@homocomputeris Sí, LAPACK implementa SVD y EVD. Y espero que las implementaciones de MKL sean extremadamente rápidas.

Sin embargo, la EVD es un poco ambigua: existe el problema de los valores propios generalizados y una EVD hermitiana (o simétrica). También hay un EVD tridiagonal, pero por lo general muy pocas personas están interesadas en eso, excepto las personas que están implementando el EVD Hermitian.

NumPy dejará de admitir Python 2.7 y 3.4 a fines de 2018, lo que permitirá el uso de compiladores MSVC más recientes que cumplan con C99, esa es una de las razones por las que estamos haciendo ese movimiento antes de que se agote el soporte oficial 2.7. Consulte http://tinyurl.com/yazmczel para obtener más información sobre la compatibilidad con C99 en compiladores recientes de MSVC. El soporte no es completo (¿alguien está completo?) Pero puede ser suficiente. En algún momento trasladaremos NumPy a C99, ya que algunos (Intel) lo han solicitado para mejorar el soporte numérico.

@charris Gracias por esta información, Charles. Es probable que todo lo que se implemente sea suficiente, pero no lo sabremos con seguridad hasta que lo intentemos.

En cualquier caso, BLIS no necesita vencer a MKL para comenzar a obtener una adopción generalizada; su competidor inmediato es OpenBLAS.

BLIS no necesita vencer a MKL para comenzar a obtener una adopción generalizada; su competidor inmediato es OpenBLAS.

No podría estar más de acuerdo. MKL está en una liga propia.

@njsmith Solo por curiosidad: ¿Qué tan interesada estaría la comunidad numpy en las nuevas API / funcionalidad que le permitieran a uno realizar, por ejemplo, gemm en precisión mixta y / o dominio mixto? Es decir, cada operando podría ser de un tipo de datos diferente, con el cálculo realizado con (potencialmente) una precisión diferente a la de uno o ambos de A y B?

@fgvanzee Interesado, sí, aunque el número potencial de combinaciones desconcierta. Pero por el momento hacemos conversiones de tipos, lo que lleva tiempo y devora memoria. También he jugado con la idea de tener rutinas optimizadas para tipos enteros, aunque boolean podría ser el único tipo que no sufriría irreparablemente por el desbordamiento.

Y siempre hay float16. La gente de ML podría estar más interesada en esas funcionalidades. @GaelVaroquaux ¿Pensamientos?

@charris Sí, la cantidad de casos puede ser abrumadora. Hay 128 combinaciones de tipos para gemm , asumiendo los cuatro tipos de datos tradicionales de punto flotante, y ese número excluye cualquier inflación combinatoria de los parámetros de transposición / conjugación, así como las posibilidades de almacenamiento de matrices (en cuyo caso subiría a 55.296).

La implementación completa de los 128 casos incluso a través de una implementación de referencia de bajo rendimiento sería digna de mención, y hacerlo de una manera de mayor rendimiento que minimice los gastos generales sería bastante especial. (Y, gracias a la base basada en objetos de BLIS, si tuviéramos que implementar esto, costaría muy poco en términos de código de objeto adicional).

Si esto le interesa, vea nuestro proyecto en los próximos días / semanas. :)

¿Es segura la horquilla BLIS?

En cualquier caso, BLIS no necesita vencer a MKL para comenzar a obtener una adopción generalizada; su competidor inmediato es OpenBLAS.

En efecto. Para ser honesto, no pude hacer que NumPy + BLAS funcionara en mi sistema, pero parecen tener un rendimiento muy similar, a juzgar por el artículo que cité antes.
Probablemente, libFLAME puede acelerar las operaciones LAPACK si está vinculado a NumPy.

Otra pregunta interesante es comparar la bifurcación BLIS / libFLAME de AMD en sus últimas CPU Zen para ver si obtuvieron alguna mejora. Se vuelve aún más interesante a la luz de los procesadores Intel problemáticos.

En Linux y Windows, las ruedas oficiales de Numpy actualmente incluyen una copia prediseñada de OpenBLAS, por lo que en esas plataformas la forma más rápida de obtener una copia es pip install numpy . (Por supuesto, esto no es del todo justo porque, por ejemplo, esa versión está construida con un compilador diferente al utilizado para construir BLIS localmente, pero puede dar una idea).

¿Es segura la horquilla BLIS?

@jakirkham Hablé con @devinamatthews sobre esto, y parece pensar que la respuesta es sí. BLIS genera subprocesos bajo demanda (por ejemplo, cuando se invoca gemm ) en lugar de mantener un grupo de subprocesos como lo hace OpenBLAS.

Sin embargo, tenemos curiosidad: ¿de qué tipo de modelo de subprocesamiento espera / prefiere / depende Numpy, si lo hay? ¿Necesita un grupo de subprocesos para reducir la sobrecarga en el caso de llamar a muchos problemas muy pequeños en sucesión? (El subprocesamiento bajo demanda de BLIS no es adecuado para ese tipo de uso).

EDITAR: En una línea similar, ¿necesita pthreads o está dispuesto a usar OpenMP?

Gracias por la info.

Entonces, ¿está usando pthreads, OpenMP, ambos? FWIW hay problemas conocidos con OpenMP de GCC y bifurcando que AFAIK no están resueltos (cc @ogrisel ). Ser como bifurcación a través del módulo multiprocessing en Python es una de las estrategias de paralelismo más comunes, ser capaz de usar subprocesos (preferiblemente pthreads por la razón anterior) de una manera segura para la bifurcación es bastante importante en Python en general. Aunque hay más opciones en estos días con cosas como loky, por ejemplo, que usan fork-exec.

Es cierto que Nathaniel sabría mucho más sobre esto que yo, pero como ya comencé a escribir este comentario, IIUC NumPy no usa subprocesos en sí, excepto a través de bibliotecas externas (por ejemplo, BLAS). Aunque NumPy lanza GIL con bastante frecuencia, lo que permite que el subproceso en Python sea algo efectivo. Cosas como Joblib, Dask, etc. aprovechan esta estrategia.

En cuanto a los grupos de subprocesos, es interesante que pregunte sobre esto, ya que el otro día estaba perfilando una técnica de ML para maximizar el rendimiento que hace una serie de rutinas BLAS en Python con NumPy / SciPy. Usando OpenBLAS y un número razonable de núcleos, esta rutina es capaz de saturar los núcleos durante la duración de la rutina, aunque no se usa ningún subproceso explícito en Python (incluidos NumPy y SciPy) simplemente porque OpenBLAS usó un grupo de subprocesos para la longitud rutina. Así que sí, los grupos de subprocesos son increíblemente valiosos.

En un punto diferente, ¿BLIS maneja la detección de arquitectura dinámica? Esto es muy importante para construir artefactos que se pueden construir una vez e implementar en una variedad de sistemas diferentes.

@jakirkham Gracias por tus comentarios. Supongo que el costo observado de no tener un grupo de subprocesos dependerá del tamaño de los operandos de la matriz que esté pasando y de la operación que esté realizando. Supongo que gemm es una operación típica a la que apunta (corríjala si me equivoco), pero ¿qué tamaño de problema considera que es típico? Me imagino que el costo del modelo de creación / unión de BLIS se manifestaría si estuvieras haciendo repetidamente una multiplicación de matrices donde A, B y C fueran 40x40, pero tal vez no tanto para 400x400 o más. (TBH, para empezar, probablemente no debería esperar mucha velocidad al paralelizar los problemas de 40x40).

En un punto diferente, ¿BLIS maneja la detección de arquitectura dinámica? Esto es muy importante para construir artefactos que se pueden construir una vez e implementar en una variedad de sistemas diferentes.

Si. Esta característica se implementó no hace mucho tiempo, en la segunda mitad de 2017. BLIS ahora puede apuntar a las llamadas "familias de configuración", con la subconfiguración específica que se utilizará elegida en tiempo de ejecución mediante alguna heurística (por ejemplo, cpuid instrucción). Ejemplos de familias apoyadas son intel64 , amd64 , x86_64 .

@fgvanzee En realidad, creo que el rendimiento en matrices pequeñas es uno de los puntos débiles de OpenBLAS, por lo que es un poco preocupante si BLIS vuelve a ser más lento ... La gente definitivamente usa numpy en todo tipo de situaciones, por lo que realmente valoramos las bibliotecas que "simplemente funciona" sin necesidad de ajustes para casos específicos. (Supongo que esto es un poco diferente de la configuración clásica de álgebra lineal densa, donde los dos casos más importantes son el autor del algoritmo que ejecuta los puntos de referencia y los expertos que ejecutan trabajos de supercomputadora de una semana con administradores de sistemas especializados). Matrices 3x3.

En algunos casos, manejar esto de manera adecuada puede ser solo una cuestión de darse cuenta de que tiene un problema demasiado pequeño y omitir el hilo por completo. Para una gema de 3x3, lo óptimo probablemente sea ser lo más estúpido posible.

Sin embargo, este tipo de ajustes (o incluso la cosa del grupo de subprocesos versus el grupo de no subprocesos) podría ser algo en lo que la comunidad se lanzaría y haría si BLIS alguna vez comienza a tener una implementación generalizada.

En realidad, eso me recuerda: la semana pasada estuve hablando con alguien que sabía que en su biblioteca quería invocar gemm single-thread, porque estaba administrando subprocesos en un nivel superior, y estaba frustrado de que con las bibliotecas estándar de blas la única forma de controlar esto es a través de configuraciones globales que son bastante fáciles de llamar desde dentro de una biblioteca aleatoria. ¿La API nativa de BLIS permite al usuario controlar la configuración de subprocesos llamada por llamada? Supongo que sí, porque IIRC no tiene ninguna configuración global, fuera de la capa de compatibilidad BLAS.

De hecho, creo que el rendimiento en matrices pequeñas es uno de los puntos débiles de OpenBLAS, por lo que es un poco preocupante si BLIS vuelve a ser más lento ... La gente definitivamente usa numpy en todo tipo de situaciones, por lo que realmente valoramos las bibliotecas que "solo funciona "sin necesidad de ajuste para casos específicos.

Entiendo que quiera que "simplemente funcione", pero estoy tratando de ser realista y honesto contigo. Si tiene un problema de 3x3 y el rendimiento es realmente importante para usted, probablemente ni siquiera debería usar BLIS. No estoy diciendo que BLIS se ejecute 10 veces más lento que un bucle triple ingenuo para 3x3, solo que ese no es el tamaño del problema en el que sobresalen las implementaciones de BLIS.

Me sorprende saber que no está satisfecho con OpenBLAS por pequeños problemas. Cual es tu punto de referencia? ¿Es solo que el rendimiento es inferior al de los problemas grandes? Lograr el mayor rendimiento posible para problemas pequeños requiere una estrategia diferente a la de problemas grandes, por lo que la mayoría de los proyectos se enfocan en uno u otro, y luego solo manejan los casos no enfocados de manera subóptima.

En algunos casos, manejar esto de manera adecuada puede ser solo una cuestión de darse cuenta de que tiene un problema demasiado pequeño y omitir el hilo por completo. Para una gema de 3x3, lo óptimo probablemente sea ser lo más estúpido posible.

Estoy de acuerdo en que un corte es ideal. Pero determinar dónde debería estar ese límite no es trivial y ciertamente varía según la arquitectura (y entre las operaciones de nivel 3). Así que es muy posible, pero aún no se ha implementado (o no se ha pensado bien).

Sin embargo, este tipo de ajustes (o incluso la cosa del grupo de subprocesos versus el grupo de no subprocesos) podría ser algo en lo que la comunidad se lanzaría y haría si BLIS alguna vez comienza a tener una implementación generalizada.

Si.

En realidad, eso me recuerda: la semana pasada estuve hablando con alguien que sabía que en su biblioteca quería invocar gemm single-thread, porque estaba administrando subprocesos en un nivel superior, y estaba frustrado de que con las bibliotecas estándar de blas la única forma de controlar esto es a través de configuraciones globales que son bastante fáciles de llamar desde dentro de una biblioteca aleatoria.

El gemm secuencial de una aplicación multiproceso es uno de nuestros casos de uso favoritos para pensar. (Recordatorio: si el gemm secuencial es su único caso de uso, simplemente puede configurar BLIS con el subproceso múltiple desactivado. Pero supongamos que quiere compilar una vez y luego decidir el subproceso más tarde).

¿La API nativa de BLIS permite al usuario controlar la configuración de subprocesos llamada por llamada? Supongo que sí, porque IIRC no tiene ninguna configuración global, fuera de la capa de compatibilidad BLAS.

Incluso si configura BLIS con multiproceso habilitado, el paralelismo está deshabilitado (un hilo) de forma predeterminada. Así que esa es una segunda forma en que puede resolver su problema.

Pero supongamos que quiere cambiar el grado de paralelismo en tiempo de ejecución. El paralelismo en BLIS se puede configurar en tiempo de ejecución. Sin embargo, todavía implica que BLIS establezca y lea internamente las variables de entorno a través de setenv() y getenv() . (Consulte la wiki de Multithreading para obtener más detalles). Así que no estoy seguro de si eso es un factor decisivo para su amigo. Queremos implementar una API en la que el subproceso se pueda especificar de una manera más programática (que no involucre variables de entorno), pero aún no lo hemos logrado. Principalmente se trata de la interfaz; la infraestructura está en su lugar. Parte del problema es que todos hemos sido entrenados a lo largo de los años (por ejemplo, OMP_NUM_THREADS , etc.) para especificar un número al paralelizar, y eso es una simplificación excesiva de la información que BLIS prefiere; hay cinco bucles en nuestro algoritmo gemm , cuatro de los cuales se pueden paralelizar (cinco en el futuro). Podemos adivinar a partir de un número, pero generalmente no es ideal, ya que depende de la topología del hardware. Así que eso es parte de lo que está obstaculizando el progreso en este frente.

@devinamatthews ¿ Alguna posibilidad de agregar TBLIS en la misma línea?

Si tiene un problema de 3x3 y el rendimiento es realmente importante para usted, probablemente ni siquiera debería usar BLIS. No estoy diciendo que BLIS se ejecute 10 veces más lento que un bucle triple ingenuo para 3x3, solo que ese no es el tamaño del problema en el que sobresalen las implementaciones de BLIS.

No estoy tan preocupado por obtener un rendimiento óptimo para problemas de 3x3 (¡aunque obviamente es bueno si podemos obtenerlo!). Pero para dar un ejemplo extremo: si numpy se compila con BLIS como su biblioteca de álgebra lineal subyacente, y algún usuario escribe a @ b en su código usando numpy, definitivamente espero que no termine funcionando 10 veces más lento que una implementación ingenua. Esperar que los usuarios reconstruyan numpy antes de multiplicar matrices 3x3 es demasiado pedir :-). Sobre todo porque el mismo programa que multiplica matrices de 3x3 en un lugar también puede multiplicar matrices de 1000x1000 en otro lugar.

Recordatorio: si gemm secuencial es su único caso de uso, simplemente puede configurar BLIS con el subproceso múltiple desactivado. Pero supongamos que quiere construir una vez y luego decidir enhebrar más tarde.

Está enviando una biblioteca de Python. Espera que sus usuarios tomen su biblioteca y la combinen con otras bibliotecas y su propio código, todos corriendo juntos en el mismo proceso. Su biblioteca usa GEMM, y es probable que algún otro código que no controla, o que ni siquiera conoce, también quiera usar GEMM. Quiere poder controlar el subproceso de sus llamadas a GEMM, sin afectar accidentalmente a otras llamadas no relacionadas a GEMM que puedan estar sucediendo en el mismo proceso. E idealmente, podría hacer esto sin tener que enviar su propia copia de GEMM, ya que es muy molesto hacerlo correctamente, y también es un poco conceptualmente ofensivo que un programa tenga que incluir dos copias de una biblioteca grande solo para que pueda obtener dos copias de una sola variable entera number_of_threads . ¿Eso tiene más sentido?

Definitivamente espero que no termine funcionando 10 veces más lento que una implementación ingenua.

Ahora que lo pienso, no me sorprendería si fuera varias veces más lento que ingenuo para problemas extremadamente pequeños (3x3), pero el punto de cruce sería bajo, tal vez tan pequeño como 16x16. Y este es un problema que es fácil de poner una curita. (Me gustaría hacerlo para todas las operaciones de nivel 3).

Está enviando una biblioteca de Python. Espera que sus usuarios tomen su biblioteca y la combinen con otras bibliotecas y su propio código, todos corriendo juntos en el mismo proceso. Su biblioteca usa GEMM, y es probable que algún otro código que no controla, o que ni siquiera conoce, también quiera usar GEMM. Quiere poder controlar el subproceso de sus llamadas a GEMM, sin afectar accidentalmente a otras llamadas no relacionadas a GEMM que puedan estar sucediendo en el mismo proceso. E idealmente, podría hacer esto sin tener que enviar su propia copia de GEMM, ya que es muy molesto hacerlo correctamente, y también es un poco conceptualmente ofensivo que un programa tenga que incluir dos copias de una biblioteca grande solo para que pueda obtener dos copias de una única variable entera number_of_threads. ¿Eso tiene más sentido?

Sí, fue útil, gracias por los detalles. Parece que sería un candidato perfecto para nuestra API de subprocesos, que aún no existe. (Personalmente, creo que la convención de variables de entorno está al borde de la locura y he expresado mis sentimientos a mis colaboradores en más de una ocasión. Dicho esto, es bastante conveniente para una amplia franja de usuarios de HPC / benchmarkers, así que tendremos que venir con una API de tiempo de ejecución adecuada que no excluya el uso de la variable de entorno para que todos estén contentos).

¿Le importaría transmitirle que esto definitivamente está en nuestro radar? Me acurrucaré con Robert. Él puede estar lo suficientemente interesado como para aprobar que dedique tiempo a esto más temprano que tarde. (La API de subprocesos ha estado en su lista de deseos durante un tiempo).

La construcción de BLIS en Windows con clang.exe dirigido a MSVC ABI es ciertamente posible. Pasé unas horas y aquí están los cambios. El número de cambios necesarios fue sorprendentemente bajo en comparación con OpenBLAS.
El registro está aquí . Todas las pruebas BLIS y BLAS pasan.

@isuruf Gracias por tomarse el tiempo para investigar esto. No me considero calificado para evaluar si esto funcionará para la gente numerosa (por más de una razón), así que me referiré a ellos en términos de que satisfaga sus necesidades.

En cuanto a la solicitud de extracción, tengo algunos comentarios / solicitudes (todos bastante menores), pero iniciaré una conversación sobre la solicitud de extracción en sí.

PD: Me alegro de que hayas podido averiguar los errores f2c . Importé solo las partes suficientes de libf2c en la fuente del controlador de prueba BLAS (compilado como libf2c.a ) para que las cosas se vinculen, aunque requirió algo de piratería en los archivos de encabezado. (No me gusta mantener el código de Fortran, hasta el punto en que prefiero mirar f2c'ed C que Fortran, en caso de que no pueda decirlo).

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.

Una palabra sobre el punto de referencia utilizado anteriormente. Robert me recordó que a menos que sepamos cómo este punto de referencia (¿o numpy?) Implementa Cholesky, EVD y SVD, los resultados de esas operaciones no serán tan significativos. Por ejemplo, si se usa el código netlib LAPACK para la factorización Cholesky, el tamaño de bloque algorítmico será incorrecto (lejos del ideal). Además, dependiendo de cuán numpy se vincule a MKL, puede distorsionar aún más las cosas porque MKL tiene su propia implementación de factorización Cholesky además de la rápida gemm , por lo que puede que ni siquiera sea una manzana con manzana cuando se compara con MKL.

Sé que puede que lo hayas estado usando simplemente para tener una idea general del rendimiento, y está bien. Solo entienda que puede haber demonios escondidos en los detalles que no permitan usarlo para nada más que comparaciones muy aproximadas.

Generalmente, numpy difiere operaciones como esas a cualquier biblioteca LAPACK o similar a LAPACK que esté disponible. Es casi seguro que las compilaciones MKL utilizan el tuned fork de LAPACK que Intel envía dentro de MKL. Las compilaciones de BLIS probablemente estén usando la referencia LAPACK o algo así.

En cierto sentido, esto es justo: si está tratando de elegir una configuración numerosa para hacer SVD rápido, entonces es relevante que MKL tenga una versión ajustada disponible y BLIS no. (Creo que OpenBLAS está en el medio: envían una versión modificada de LAPACK con la biblioteca, pero está mucho más cerca de la implementación de referencia que la versión de MKL). Pero sí, seguro si estás tratando de entender qué es BLIS y por qué Los resultados se ven así, es importante recordar que hay mucho más en SVD / etc que solo la pura eficiencia de GEMM.

En cierto sentido, esto es justo:

Entiendo y estoy de acuerdo. Es complicado por el hecho de que algunas personas usan puntos de referencia como el propuesto anteriormente para medir el rendimiento absoluto en un solo hardware (es decir, para juzgar qué tan buena es una implementación en relación con el rendimiento máximo), y algunos lo usan para comparar implementaciones. Creo que los comentarios de Robert sobre Cholesky et al. están más dirigidos a los primeros que a los segundos.

@njsmith @insertinterestingnamehere @rgommers @charris Gracias al rápido trabajo de Isuru, pudimos refinar y fusionar su soporte de Windows basado en appveyor en la rama master BLIS. Eche un vistazo y háganos saber si y en qué medida esto aborda el problema del soporte de Windows para numpy.

@fgvanzee ¿Puedes vincular al PR?

¿Puedes vincular al PR?

Claro, aquí está.

¡Esa configuración de construcción inicial es genial! Al menos en teoría, debería ser suficiente para permitir construir numpy con BLIS en Windows. Más adelante se pueden agregar cosas como crear una dll o admitir MinGW.

Una cosa a tener en cuenta sobre las dependencias utilizadas en este caso: pthreads-win32 es LGPL. IDK, si es que hay algo que se debe hacer al respecto.

La concesión de licencias es un tema difícil. Conozco algunas grandes empresas cuyas políticas legales dificultan el uso de licencias "copyleft" como LGPL, mientras que las licencias "permisivas" no son un problema. Agregar código LGPL a la base del código NumPy / SciPy o a las ruedas definitivamente sería un motivo de preocupación, justificadamente o no.

No lo estamos agregando al código base aquí. Enviar un componente LGPL con ruedas no es un problema. Ahora estamos enviando una dll de gfortran que es GPL con excepción de tiempo de ejecución. Ver gh-8689

En efecto. Además, no me sorprendería que estas empresas también prefieran MKL.

Solo necesitamos asegurarnos de que el componente LGPL no esté vinculado estáticamente, probablemente.

@jakirkham Gracias por tus comentarios. Supongo que el costo observado de no tener un grupo de subprocesos dependerá del tamaño de los operandos de la matriz que esté pasando y de la operación que esté realizando. Supongo que gemm es una operación típica a la que apunta (corríjala si me equivoco), pero ¿qué tamaño de problema considera que es típico? Me imagino que el costo del modelo de creación / unión de BLIS se manifestaría si estuvieras haciendo repetidamente una multiplicación de matrices donde A, B y C fueran 40x40, pero tal vez no tanto para 400x400 o más. (TBH, para empezar, probablemente no debería esperar mucha velocidad al paralelizar los problemas de 40x40).

Principalmente, GEMM y SYRK son típicos. GEMV aparece a veces, pero a menudo se convierte en una gran operación GEMM si es posible.

No es atípico para nosotros tener al menos una dimensión del orden de 10 ^ 6 elementos. El otro podría estar entre 10 ^ 3 y 10 ^ 6. Entonces varía bastante. ¿Hay algo que debamos hacer para que esto use el grupo de subprocesos o eso sucede automáticamente? Además, ¿cómo se comporta el threadpool si el proceso se bifurca?

En un punto diferente, ¿BLIS maneja la detección de arquitectura dinámica? Esto es muy importante para construir artefactos que se pueden construir una vez e implementar en una variedad de sistemas diferentes.

Si. Esta característica se implementó no hace mucho tiempo, en la segunda mitad de 2017. BLIS ahora puede apuntar a las llamadas "familias de configuración", con la subconfiguración específica que se usará elegida en tiempo de ejecución a través de alguna heurística (por ejemplo, instrucción cpuid). Ejemplos de familias compatibles son intel64, amd64, x86_64.

Para comenzar un poco esto, tenemos computadoras viejas que usan Nehalem hasta Broadwell. Quizás algunas máquinas personales más nuevas tengan Kaby Lake. Por lo tanto, es importante poder aprovechar al máximo la arquitectura que se nos proporciona y, al mismo tiempo, no fallar si estamos ejecutando una máquina antigua mediante el uso de intrínsecos no compatibles. ¿Las subconfiguraciones de Flame admiten este rango? ¿Hay códigos adicionales integrados para distribuir el kernel adecuado en diferentes arquitecturas? ¿Qué tan granular se vuelve esto? ¿Hay algo que debamos hacer durante la compilación para garantizar que estos núcleos estén presentes?

La construcción de x86_64 te lleva a:

  • Penryn (incluido Nehalem y cualquier otro chip Intel SSSE3 de 64 bits)
  • Sandy Bridge (incluido Ivy Bridge)
  • Haswell (+ Broadwell, Skylake, Kaby Lake y Coffee Lake, aunque puede que no sea completamente óptimo para los últimos tres)
  • Xeon Phi (primera y segunda generación, también podríamos hacer la tercera generación si fuera necesario)
  • Skylake SP / X / W (probablemente también estará muy cerca del óptimo en Cannon Lake)
  • Bulldozer / Piledriver / Steamroller / Excavator
  • Ryzen / EPYC

Tenga en cuenta que algunos de estos requieren un compilador suficientemente nuevo y / o una versión de binutils, pero solo en la máquina de compilación.

AFAIK libflame no tiene kernels especializados para ninguna arquitectura, todo esto depende de BLIS.

@jakirkham tendríamos que agregar una implementación de threadpool. Por ahora, es una implementación básica de combinación de bifurcaciones que debería ser segura para las bifurcaciones.

AFAIK libflame no tiene kernels especializados para ninguna arquitectura, todo esto depende de BLIS.

Esto es mayormente correcto. Si bien libflame tiene algunos núcleos intrínsecos (actualmente solo SSE) para aplicar rotaciones de Givens, esos núcleos no pertenecen a libflame y solo están allí porque BLIS no existía en el momento en que se escribieron y, por lo tanto, no había otro lugar para albergarlos. Eventualmente, esos kernels se reescribirán y actualizarán y se reubicarán en BLIS.

¿Las subconfiguraciones de Flame admiten este rango? ¿Hay códigos adicionales integrados para distribuir el kernel adecuado en diferentes arquitecturas? ¿Qué tan granular se vuelve esto? ¿Hay algo que debamos hacer durante la compilación para garantizar que estos núcleos estén presentes?

@jakirkham No estoy seguro de a qué te refieres con "Flame" en este caso. Tenemos dos productos: libflame y BLIS. Si bien libflame necesita atención y probablemente una reescritura, prácticamente toda mi atención durante los últimos años ha estado en BLIS.

Si reemplaza textualmente "Flame" por "BLIS", la respuesta es "sí, en su mayoría".

¿Qué tan granular se vuelve?

No estoy seguro de lo que quiere decir, pero el soporte del kernel en BLIS no es tan extenso como en OpenBLAS. Por ejemplo, muchas veces no optimizamos el dominio complejo trsm . Los núcleos se utilizan, en alguna combinación, por subconfiguraciones. Las subconfiguraciones se eligen a través de cpuid . Obtiene exactamente los núcleos "registrados" por la subconfiguración. Consulte la wiki de configuración para obtener detalles sobre las tuercas y los tornillos.

¿Hay algo que debamos hacer durante la compilación para garantizar que estos núcleos estén presentes?

Si necesita detección de hardware en tiempo de ejecución, apunte a una familia de configuración (por ejemplo, intel64 , x86_64 ) en el momento de la configuración en lugar de una subconfiguración específica (o auto , que selecciona una subconfiguración específica). Eso es.

¿Hay algo que debamos hacer para que esto use el grupo de subprocesos o eso sucede automáticamente? Además, ¿cómo se comporta el threadpool si el proceso se bifurca?

Como dije anteriormente en este hilo, BLIS no usa un grupo de hilos. Y le cito a Devin la seguridad de la horquilla (y parece pensar que la horquilla no será un problema).

Para su información, los paquetes conda BLIS están disponibles para linux, osx, windows en conda-forge. (Actualmente construyendo una rama de desarrollo, esperando un lanzamiento). Los paquetes se crearon con pthreads habilitados y configuración x86_64.

Está enviando una biblioteca de Python. Espera que sus usuarios tomen su biblioteca y la combinen con otras bibliotecas y su propio código, todos corriendo juntos en el mismo proceso. Su biblioteca usa GEMM, y es probable que algún otro código que no controla, o que ni siquiera conoce, también quiera usar GEMM. Quiere poder controlar el subproceso de sus llamadas a GEMM, sin afectar accidentalmente a otras llamadas no relacionadas a GEMM que puedan estar sucediendo en el mismo proceso.

@njsmith He hablado con Robert y él está de acuerdo con aumentar la prioridad de trabajar en esto. (Además, al analizarlo brevemente, descubrí una condición de carrera en BLIS que se manifestaría cada vez que dos o más subprocesos de aplicación intentaran usar diferentes grados de paralelismo. La corrección de esa condición de carrera se adaptará simultáneamente a las necesidades relacionadas con la API de personas como tu amigo.)

@jakirkham Uno de nuestros contactos en Intel, @jeffhammond , nos informa que las implementaciones de OpenMP emplean de forma rutinaria un modelo de grupo de subprocesos internamente. Por lo tanto, nos disuade de implementar grupos de subprocesos de forma redundante dentro de BLIS.

Ahora, puede ser el caso, como sugiere, que numpy necesita / prefiere pthreads, en cuyo caso tal vez volvamos a una bifurcación / unión real que ocurre debajo de la API de pthreads de estilo fork / join.

Entonces, ¿está usando pthreads, OpenMP, ambos?

Además, me di cuenta de que olvidé responder a esta pregunta. El paralelismo multiproceso de BLIS es configurable: puede usar pthreads u OpenMP. (Sin embargo, esto no debe confundirse con la dependencia incondicional del tiempo de ejecución de BLIS en pthreads debido a nuestra dependencia de pthread_once() , que se utiliza para la inicialización de la biblioteca).

Desafortunadamente, a menos que las implementaciones de OpenMP (por ejemplo, GOMP) se vuelvan más robustas a la bifurcación, no es realmente una opción segura en Python. Particularmente no en algo tan bajo en la pila como NumPy.

Desafortunadamente, a menos que las implementaciones de OpenMP (por ejemplo, GOMP) se vuelvan más robustas a la bifurcación, no es realmente una opción segura en Python. Particularmente no en algo tan bajo en la pila como NumPy.

Lo suficientemente justo. Entonces, parece que numpy se basaría en la opción de configuración --enable-threading=pthreads para el subproceso múltiple a través de BLIS.

Cuando se compila con pthreads, ¿hay alguna API pública para obtener algún control programático para evitar problemas de suscripción excesiva al hacer paralelismo anidado con procesos de Python / grupos de subprocesos que llaman a numpy?

Más específicamente, aquí están los tipos de símbolos públicos que buscaría:

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

Similar a lo que se hace en https://github.com/IntelPython/smp para MKL y OpenMP.

Cuando se compila con pthreads, ¿hay alguna API pública para obtener algún control programático para evitar problemas de suscripción excesiva al hacer paralelismo anidado con procesos de Python / grupos de subprocesos que llaman a numpy?

@ogrisel Gran pregunta, Olivier. Hay una forma de establecer parámetros de subprocesamiento en tiempo de ejecución, pero actualmente se hace con semántica global. Es subóptimo porque (además de ser global en lugar de por llamada a gemm o lo que sea) se alimenta a través de variables de entorno.

Actualmente estoy trabajando en un rediseño menor del código subyacente que permitirá a alguien usar una de las llamadas sub-APIs "expertas" de BLIS para pasar una estructura de datos adicional a gemm que permitirá la llamador para especificar por llamada cuál debería ser la estrategia de paralelización. La estructura de datos antes mencionada permitirá a las personas especificar un solo número de subprocesos y permitirá que BLIS haga todo lo posible para averiguar dónde obtener paralelismo, o un nivel de paralelismo para cada ciclo en el algoritmo de multiplicación de matrices (como BLIS prefiere).

De cualquier manera, creo que este nuevo enfoque satisfará sus necesidades. Ya estoy a mitad de camino con los cambios, así que creo que solo estoy una semana más o menos para que la nueva función se envíe a github. Por favor, avíseme si este plan parece ser algo que funcione para usted y / o si tiene otras inquietudes o solicitudes sobre este tema.

PD: Eché un vistazo al enlace loky que proporcionaste. Todos esos símbolos están configurados para una configuración global de hilos. Mi solución propuesta no excluiría la configuración global de las cosas, pero está diseñada para que esa no sea la única opción. Entonces, alguien que quiera usar un máximo de 8 núcleos / subprocesos podría hacer que una aplicación genere 2 subprocesos, y cada uno de ellos invocará una instancia de gemm que obtiene un paralelismo de 4 vías. O 3 subprocesos de aplicación donde dos de ellos llaman gemm con paralelismo de 2 vías y el tercero usa paralelismo de 4 vías. (Entiendes la idea).

se alimenta a través de variables de entorno.

Qué significa eso? ¿Es posible usar ctypes de Python para reconfigurar el tamaño del grupo de subprocesos BLIS predeterminado / global una vez que ya se ha inicializado en un proceso específico de Python?

se alimenta a través de variables de entorno.

Qué significa eso?

@ogrisel Lo que quiero decir con esto es que tenemos una pequeña API en BLIS para establecer u obtener el número de subprocesos, similar a omp_get_num_threads() y omp_set_num_threads() en OpenMP. Sin embargo, estas funciones de la API de BLIS se implementan como llamadas a getenv() y setenv() . Esto es simplemente un artefacto histórico del hecho de que uno de los casos de uso originales de BLIS fue establecer una o más variables de entorno en el shell (por ejemplo, bash ) y luego ejecutar una aplicación vinculada a BLIS, por lo que en el Es hora de que sea conveniente simplemente basarse en ese enfoque de variable de entorno; nunca tuvo la intención de ser la forma definitiva y perfecta de especificar el número de hilos para el paralelismo.

¿Es posible usar ctypes de Python para reconfigurar el tamaño del grupo de subprocesos BLIS una vez que ya se ha inicializado en un proceso específico de Python?

BLIS no utiliza explícitamente un grupo de subprocesos. Más bien, utilizamos un modelo de creación / unión para extraer paralelismo a través de pthreads. Pero sí, después de que se haya inicializado BLIS, la aplicación (o biblioteca de llamadas) puede cambiar el número de subprocesos en tiempo de ejecución mediante una llamada a función, al menos en principio. (No sé si esto funcionaría en la práctica , sin embargo, porque no sé cómo Python manejaría BLIS intentando establecer / obtener variables de entorno). Pero como mencioné anteriormente, una vez que complete algunas modificaciones, la aplicación / La biblioteca podrá especificar un esquema de paralelización personalizado por llamada en el momento en que se llame a la operación de nivel 3. Esto se haría primero inicializando un pequeño tipo de datos struct y luego pasando ese struct a una versión extendida "experta" de la API BLIS por gemm , por ejemplo) haría que las variables de entorno fueran innecesarias para aquellos que no las necesitan / quieren. Espero que esto responda a tu pregunta.

Muchas gracias por las aclaraciones. La API experta por llamada es interesante, pero requeriría mucho para mantener una API específica para exponerla a sus llamadores de nivel Python. No estoy seguro de que queramos eso. Creo que para muchos usuarios tener una forma de cambiar el valor actual del nivel de paralelismo global (nivel de proceso) es suficiente. Personalmente, no me importa si se logra cambiando el valor actual de la variable env, siempre que este cambio se tenga en cuenta para las llamadas BLAS-3 posteriores.

@charris float16 podría ser interesante para algunas cargas de trabajo de aprendizaje automático, al menos en el momento de la predicción, aunque no tengo experiencia personal con esto.

Creo que para muchos usuarios tener una forma de cambiar el valor actual del nivel de paralelismo global (nivel de proceso) es suficiente. Personalmente, no me importa si se logra cambiando el valor actual de la variable env, siempre que este cambio se tenga en cuenta para las llamadas BLAS-3 posteriores.

Bueno saber. Si ese es el caso, entonces BLIS está mucho más cerca de estar listo para usar por numpy. (Solo me gustaría hacer estas modificaciones en progreso primero, que también solucionan una condición de carrera previamente desapercibida).

Probablemente sea mejor no depender de que las variables de entorno se vuelvan a verificar en cada llamada, porque getenv no es muy rápido, por lo que podría tener sentido eliminarlo más tarde. (Además, ¿no creo que esté garantizado que sea seguro para subprocesos?) Pero las llamadas a la API bli_thread_set_num_threads deberían estar bien, ya que incluso si BLIS deja de llamar a getenv todo el tiempo, la API llama se puede ajustar para seguir trabajando independientemente.

A largo plazo, creo que tendría sentido comenzar a exponer algunas API más allá de BLAS en numpy. Una de las cosas que hace que BLIS sea atractivo en primer lugar es exactamente que proporciona características que otras bibliotecas BLAS no tienen, como la capacidad de multiplicar matrices escalonadas, y se está trabajando para extender las API BLAS de varias maneras .

No querríamos codificar detalles específicos de la biblioteca en la API de numpy (por ejemplo, no querríamos que np.matmul comience a tomar argumentos correspondientes a los parámetros JC, IC, JR e IR de BLIS ), pero Bien podría tener sentido proporcionar un argumento genérico de "cuántos subprocesos para esta llamada" que solo funcione en backends que brinden esa funcionalidad.

Una cosa que no he visto mencionada es la precisión del índice. La mayoría de las bibliotecas suministradas por el sistema parecen utilizar enteros de 32 bits, lo que es una limitación para algunas aplicaciones en estos días. En algún momento, sería bueno que todos los índices fueran de 64 bits, lo que probablemente requiere que proporcionemos la biblioteca. No sé qué estamos haciendo actualmente con respecto al tamaño del índice. @ matthew-brett ¿Seguimos compilando con enteros de 32 bits?

@charris El tamaño entero en BLIS se puede configurar en el momento de la configuración: 32 o 64 bits. Además, puede configurar el tamaño de entero utilizado en la API de BLAS independientemente del tamaño de entero interno.

En realidad, eso me recuerda: la semana pasada estuve hablando con alguien que sabía que en su biblioteca quería invocar gemm single-thread, porque estaba administrando subprocesos en un nivel superior, y estaba frustrado de que con las bibliotecas estándar de blas la única forma de controlar esto es a través de configuraciones globales que son bastante fáciles de llamar desde dentro de una biblioteca aleatoria.

@njsmith He solucionado la condición de carrera que mencioné anteriormente y también implementé la API multiproceso por llamada segura para subprocesos. Dirija a su amigo hacia fa08e5e (o cualquier descendiente de esa confirmación). La documentación de subprocesos múltiples también se ha actualizado y guía al lector a través de sus opciones, con ejemplos básicos. La confirmación está en la rama dev por ahora, pero espero fusionarla a master pronto. (Ya he puesto a prueba el código en la mayoría de sus pasos).

EDITAR: Enlaces actualizados para reflejar un compromiso de corrección menor.

Como posible adición a los tipos admitidos, ¿qué pasa con long double ? Observo que algunas arquitecturas están comenzando a admitir precisión cuádruple (todavía en software) y espero que en algún momento la precisión extendida sea reemplazada por precisión cuádruple en Intel. No creo que esto sea urgente de inmediato, pero creo que después de todos estos años las cosas están empezando a ir de esa manera.

@charris Estamos en las primeras etapas de considerar el soporte para bfloat16 y / o float16 en particular debido a sus aplicaciones de aprendizaje automático / IA, pero también somos conscientes de la demanda de double double y precisión cuádruple. Necesitaríamos sentar algunas bases para que sea factible para todo el marco, pero definitivamente está en nuestro radar a mediano y largo plazo.

@charris Según https://en.wikipedia.org/wiki/Long_double , long double puede significar una variedad de cosas:

  • Tipo de 80 bits implementado en x87, usando almacenamiento 12B o 16B
  • doble precisión con MSVC
  • doble doble precisión
  • precisión cuádruple
    Debido a que el significado es ambiguo y depende no solo del hardware sino del compilador utilizado, es un desastre total para las bibliotecas, porque la ABI no está bien definida.

Desde una perspectiva de rendimiento, no veo ninguna ventaja en float80 (es decir, x87 long double) porque no hay una versión SIMD. Si uno puede escribir una versión SIMD de double double en BLIS, debería funcionar mejor.

La implementación de float128 en software es al menos un orden de magnitud más lenta que float64 en hardware. Sería prudente escribir una nueva implementación de float128 que omita todo el manejo de FPE y sea compatible con SIMD. La implementación en libquadmath, aunque correcta, no merece la atención de una implementación BLAS de alto rendimiento como BLIS.

Sí, es un problema. No creo que la precisión extendida valga la pena, y la necesidad de precisión cuádruple es irregular, el doble es bueno para la mayoría de las cosas, pero cuando lo necesitas, lo necesitas. No me preocupa el rendimiento, no se necesita velocidad, sino precisión. Tenga en cuenta que acabamos de ampliar el soporte a un ARM64 con cuádruple precisión long double, una implementación de software, por supuesto, pero espero que el hardware siga en algún momento y sería bueno tener algo probado y listo para funcionar.

La propuesta BLAS G2 tiene alguna consideración de cálculos doble-doble y "reproducibles" en BLAS. (Reproducible aquí significa determinista en todas las implementaciones, pero IIUC también implica el uso de valores intermedios de mayor precisión).

¡Emocionado de ver que esto avanza!

Para que conste, soy a quien @njsmith se refería que estaba interesado en controlar el subproceso desde el software. Mis cargas de trabajo son vergonzosamente paralelas en el momento de la predicción, y mis multiplicaciones de matrices son relativamente pequeñas. Así que prefiero paralelizar unidades de trabajo más grandes.

Hice un trabajo hace aproximadamente un año en el empaquetado de Blis para PyPi y agregando enlaces Cython: https://github.com/explosion/cython-blis

Encontré Blis bastante fácil de empaquetar como una extensión C como esta. El principal obstáculo para mí fue el soporte de Windows. De memoria, eran problemas de C99, pero podría estar recordando mal.

La interfaz Cython que agregué podría ser de interés. En particular, estoy usando los tipos fusionados de Cython para que haya una única función nogil que se pueda llamar con una vista de memoria o un puntero sin formato, tanto para el tipo flotante como para el doble. Agregar más ramas para más tipos tampoco es un problema. Los tipos fusionados son básicamente plantillas: permiten la ejecución condicional en tiempo de compilación, sin gastos generales.

Estaría muy feliz de mantener un paquete Blis independiente, mantener las ruedas construidas, mantener una buena interfaz Cython, etc. Creo que sería muy bueno tenerlo como un paquete separado, en lugar de algo integrado dentro de numpy. Entonces podríamos exponer más API de Blis, sin estar limitados por lo que admiten otras bibliotecas BLAS.

@honnibal Perdón por la demora en responder a este hilo, Matthew.

Gracias por tu mensaje. Siempre estamos felices de ver a otros entusiasmados con BLIS. Por supuesto, estaremos encantados de asesorarle cuando sea necesario si decide integrarse aún más en el ecosistema de Python (una aplicación, una biblioteca, un módulo, etc.).

En cuanto al soporte de Windows, consulte el soporte de clang / appveyor para Windows ABI que @isuruf agregó recientemente. La última vez que supe de él, estaba funcionando como se esperaba, pero no hacemos ningún desarrollo en Windows aquí en UT, así que no puedo estar al tanto de esto yo mismo. (Aunque Isuru me señaló una vez que podía inscribirme en Appveyor de una manera similar a Travis CI).

Además, avíseme si tiene alguna pregunta sobre el uso de subprocesos por llamada. ( Actualicé nuestra documentación de

A partir de BLIS 0.5.2 , tenemos un documento de rendimiento que muestra el

Entonces, si la comunidad numpy se pregunta cómo se compara BLIS con las otras soluciones BLAS líderes, ¡lo invito a echar un vistazo rápido!

Muy interesante, gracias @fgvanzee.

Tuve que buscar Epyc, parece que ese es un nombre de marca basado en la arquitectura Zen (¿posiblemente actualizada a Zen + en algún momento?). ¿Quizás sea mejor cambiar el nombre a Zen? Para nuestra base de usuarios, Ryzen / Threadripper son las marcas más interesantes, pueden reconocer Zen pero probablemente no Epyc.

Epyc es el nombre de la línea de servidores AMD. Es el sucesor de los productos AMD Opteron del pasado.

Desafortunadamente, no existe una forma única para que BLIS etiquete sus objetivos arquitectónicos, porque el código depende del vector ISA (por ejemplo, AVX2), la microarquitectura del núcleo de la CPU (por ejemplo, Ice Lake) y la integración de SOC / plataforma (por ejemplo, procesador Intel Xeon Platinum ). BLIS usa nombres de código de microarquitectura en algunos casos (por ejemplo, Dunnington), pero eso no es mejor para todos.

@fgvanzee Podría considerar agregar los alias que corresponden a los nombres de GCC march / mtune / mcpu ...

@rgommers La subconfiguración dentro de BLIS que cubre Ryzen y Epyc ya se llama zen , ya que captura ambos productos.

En cuanto a si Ryzen / Threadripper o Epyc son marcas más interesantes (incluso para muchos usuarios), diré esto: si solo pudiera comparar un sistema AMD Zen, sería el Epyc de gama más alta, porque: (a) utiliza una microarquitectura similar a la de Ryzen; (b) me da el máximo de 64 núcleos físicos (y, como beneficio adicional, esos núcleos están dispuestos en una configuración algo novedosa, similar a NUMA); que (c) coloca el máximo estrés en BLIS y las otras implementaciones. Y eso es básicamente lo que hicimos aquí.

Ahora, afortunadamente, no hay ninguna regla que diga que solo puedo comparar un sistema Zen. :) Sin embargo, existen otros obstáculos, particularmente en lo que respecta a obtener acceso en primer lugar. No tengo acceso a ningún sistema Ryzen / Threadripper en este momento. Si / cuando obtengo acceso, estaré encantado de repetir los experimentos y publicar los resultados en consecuencia.

Jeff señala algunos de los problemas de nomenclatura que enfrentamos. Generalmente, nombramos nuestras subconfiguraciones y conjuntos de kernel en términos de microarquitectura, pero todavía hay más matices. Por ejemplo, usamos nuestra subconfiguración haswell en Haswell, Broadwell, Skylake, Kaby Lake y Coffee Lake. Eso es porque básicamente todos comparten el mismo vector ISA, que es prácticamente todo lo que le importa al código del kernel de BLIS. Pero ese es un detalle de implementación del que casi ningún usuario debe preocuparse. Si usa ./configure auto , casi siempre obtendrá la mejor subconfiguración y conjunto de kernel para su sistema, ya sea que se llamen zen o haswell o lo que sea. Por ahora, todavía necesita adoptar un enfoque más práctico cuando se trata de elegir de manera óptima su esquema de subprocesos, y ahí es donde entra en juego la integración de SoC / plataforma que menciona Jeff.

@jeffhammond Gracias por tu sugerencia. He considerado agregar esos alias en el pasado. Sin embargo, no estoy convencido de que valga la pena. Agregará un desorden significativo al registro de configuración, y las personas que lo verán en primer lugar probablemente ya conozcan nuestro esquema de nombres para subconfiguraciones y conjuntos de kernel, y por lo tanto no se confundirán por la ausencia de cierta revisión de microarquitectura nombres en ese archivo (o en el directorio config ). Ahora, si BLIS requirió la identificación manual de la subconfiguración, a través de ./configure haswell por ejemplo, entonces creo que la balanza definitivamente se inclina a favor de su propuesta. Pero ./configure auto funciona bastante bien, por lo que no veo la necesidad en este momento. (Si lo desea, puede abrir un número sobre este tema para que podamos iniciar una discusión más amplia entre los miembros de la comunidad. Siempre estoy dispuesto a cambiar de opinión si hay suficiente demanda).

sí, nombrar siempre es complicado :) gracias por las respuestas @fgvanzee y @jeffhammond

# 13132 y # 13158 están relacionados

La discusión se dejó llevar un poco; ¿Cuáles son los problemas restantes que deben resolverse para admitir oficialmente BLIS en numpy?

Ingenuamente, intenté ejecutar numerosas pruebas con BLIS desde conda-forge (cf https://github.com/numpy/numpy/issues/14180#issuecomment-525292558) y para mí, en Linux, todas las pruebas pasaron (pero tal vez se perdió algo).

También intenté ejecutar el conjunto de pruebas scipy de prueba en el mismo entorno y hay una serie de fallas en scipy.linalg (cf https://github.com/scipy/scipy/issues/10744) en caso de que alguien tenga comentarios sobre ese.

Tenga en cuenta que BLIS de conda-forge usa ReferenceLAPACK (netlib) como implementación LAPACK que usa BLIS como implementación BLAS y no libflame .

Acerca de la opción BLIS en conda-forge, ¿tengo razón en que es de un solo subproceso listo para usar (a diferencia de la opción OpenBLAS)?

Probablemente sea mejor mover las discusiones de conda-forge a conda-forge 🙂

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