Numpy: Problème de suivi pour la prise en charge BLIS dans NumPy

Créé le 2 mars 2016  ·  97Commentaires  ·  Source: numpy/numpy

Voici un problème de suivi général pour discuter de la prise en charge de BLIS dans NumPy. Jusqu'à présent, nous avons eu quelques discussions à ce sujet dans deux fils nominalement non liés:

C'est donc un nouveau numéro pour consolider les discussions futures comme celle-ci :-)

Quelques problèmes actuellement en suspens à souligner:

CC: @tkelman @ matthew-brett @fgvanzee

15 - Discussion build

Commentaire le plus utile

Construire BLIS sur Windows avec clang.exe ciblant MSVC ABI est certainement possible. J'ai passé quelques heures et voici les changements. Le nombre de changements requis était étonnamment faible par rapport à OpenBLAS.
Le journal est ici . Tous les tests BLIS et BLAS réussissent.

Tous les 97 commentaires

Je vois que BLIS a été inclus dans site.cfg .
La libFLAME peut-elle également être prise en charge?

La description sur https://www.cs.utexas.edu/%7Eflame/web/libFLAME.html peut nécessiter une correction, mais à partir de là, il n'est pas clair pour moi que libFLAME implémente réellement l'API LAPACK.

@rgommers libflame fournit en effet des API netlib LAPACK et des implémentations pour tout ce que libflame ne fournit pas nativement.

FWIW, BLIS a fait des progrès significatifs depuis l'ouverture de ce numéro. Par exemple, BLIS implémente désormais la configuration d'exécution, et sa configuration au moment de la configuration a été réimplémentée en termes d'infrastructure d'exécution. BLIS propose désormais des pilotes de test BLAS intégrés en plus de sa suite de tests plus complète. L'auto-initialisation de la bibliothèque est également en place, tout comme la génération d'en-têtes monolithiques (blis.h unique au lieu de plus de 500 en-têtes de développement), ce qui facilite la gestion du produit installé. Il suit également une convention de dénomination de bibliothèque plus standardisée pour ses versions de bibliothèques statiques et partagées, comprenant un soname . Enfin, son système de construction est beaucoup plus intelligent vis-à-vis de la vérification de la compatibilité du compilateur et de l'assembleur. Et c'est exactement ce à quoi je peux penser du haut de ma tête.

@fgvanzee merci. Dans ce cas, je suis +1 pour ajouter le support pour cela dans numpy.distutils .

Je suis vraiment à court de temps pour le moment, donc je ne peux pas travailler là-dessus probablement avant septembre au moins. Si quelqu'un d'autre veut s'attaquer à cela, cela devrait être relativement simple (dans le même esprit que gh-7294). Heureux d'aider à dépanner / examiner.

Cela ressemble à un progrès majeur. Jusqu'où diriez-vous que vous êtes une alternative viable à OpenBLAS pour numpy / scipy (performances et stabilité)?

(Notez que nous n'avons toujours pas de scipy-openblas sur Windows pour conda raison du désordre Fortran: https://github.com/conda-forge/scipy-feedstock/blob/master/recipe/ meta.yaml # L14)

Si vous voulez un BLAS et un LAPACK compatibles MSVC ABI, il n'y a toujours pas d'options faciles et entièrement open source. Bien que de nos jours avec clang-cl et flang, le problème n'est pas la disponibilité du compilateur comme il l'était auparavant, maintenant c'est la flexibilité du système de construction et d'essayer d'utiliser des combinaisons que les auteurs de bibliothèques n'ont jamais évaluées ou prises en charge auparavant. Réf https://github.com/flame/blis/issues/57#issuecomment -284614032

@rgommers Je dirais que BLIS est actuellement une alternative tout à fait viable à OpenBLAS. Il est suffisamment viable qu'AMD ait abandonné ACML et ait pleinement adopté BLIS comme base de sa nouvelle solution de bibliothèque mathématique open-source. (Nous avons des entreprises sponsors et avons été parrainés par la National Science Foundation pendant de nombreuses années dans le passé.)

En termes de performances, la caractérisation exacte dépendra de l'opération que vous regardez, du type de données en virgule flottante, du matériel et de la plage de taille du problème qui vous intéresse. Cependant, en général, BLIS atteint ou dépasse généralement celui d'OpenBLAS performances de niveau 3 pour tous, sauf les plus petites tailles de problème (moins de 300 environ). Il utilise également une stratégie de parallélisation de niveau 3 plus flexible que celle d'OpenBLAS (en raison de leur conception de noyau d'assemblage monolithique).

Sur le plan de la stabilité, j'aimerais penser que BLIS est assez stable. Nous essayons d'être très réactifs aux rapports de bogues, et grâce à un regain d'intérêt de la communauté au cours des cinq derniers mois environ, nous avons pu identifier et résoudre de nombreux problèmes (principalement liés au système de construction). Cela a facilité l'expérience utilisateur pour les utilisateurs finaux ainsi que pour les gestionnaires de packages.

De plus, gardez à l'esprit que BLIS a fourni (à partir du jour zéro) un sur-ensemble de fonctionnalités de type BLAS, et ce, via deux nouvelles API distinctes de la couche de compatibilité BLAS:

  • une API de type BLAS explicitement typée
  • une API basée sur des objets typée implicitement

Cela prend non seulement en charge les utilisateurs hérités qui ont déjà un logiciel nécessitant une liaison BLAS, mais fournit une excellente boîte à outils pour ceux qui souhaitent créer des solutions d'algèbre linéaire dense personnalisées à partir de zéro - des personnes qui ne ressentent aucune affinité particulière avec l'interface BLAS.

Né d'une frustration face aux diverses lacunes des implémentations BLAS existantes ainsi que de l'API BLAS elle-même, BLIS a été mon travail d'amour depuis 2012. Il ne va nulle part et ne fera que s'améliorer. :)

Ah merci @tkelman. Cygwin :( :(

Il serait intéressant d'entendre quelques expériences de personnes utilisant numpy compilé contre BLIS sur Linux / macOS.

Merci pour le contexte @fgvanzee. Il sera intéressant pour nous d'ajouter le support de libFLAME et d'essayer la suite de référence SciPy.

@rgommers Re: libflame: Merci pour votre intérêt. Sachez simplement que libflame pourrait utiliser un certain TLC; il n'est pas aussi en forme que BLIS. (Nous n'avons pas le temps / les ressources pour le soutenir comme nous le souhaiterions, et près de 100% de notre attention au cours des six dernières années a été concentrée sur amener BLIS à un endroit où il pourrait devenir une alternative viable et compétitive à OpenBLAS. et al.)

À un moment donné, une fois que BLIS mûrit et que nos pistes de recherche auront été épuisées, nous retournerons probablement notre attention sur les fonctionnalités de niveau libflame / LAPACK (Cholesky, LU, factorisations QR, par exemple). Cela peut prendre la forme de l'ajout incrémentiel de ces implémentations à BLIS, ou cela peut impliquer un projet entièrement nouveau pour éventuellement remplacer libflame. Si c'est ce dernier, il sera conçu pour tirer parti des API de bas niveau dans BLIS, évitant ainsi certains appels de fonction et une surcharge de copie mémoire actuellement inévitable via le BLAS. Ce n’est qu’un des nombreux sujets sur lesquels nous avons hâte d’étudier.

J'ai exécuté le benchmark de cet article avec NumPy 1.15 et BLIS 0.3.2 sur un Intel Skylake sans multithreading (j'ai eu une erreur d'instruction matérielle avec 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.

Pointillé de deux matrices 4096x4096 en 4,29 s.

@homocomputeris Désolé, je n'ai jamais entendu le verbe "point" utilisé pour décrire une opération sur deux matrices auparavant. Est-ce une multiplication matricielle?

@fgvanzee Quel est l'état de la prise en charge de Windows dans BLIS ces jours-ci? Je me souviens que le fait de le construire sur Windows n'était généralement pas pris en charge ...

@fgvanzee et oui, numpy.dot est la manière traditionnelle d'appeler GEMM en Python. (Une sorte de nom étrange, mais c'est parce qu'il gère le vecteur-vecteur, la matrice vectorielle, la matrice-matrice le tout dans la même API.)

@njsmith L'état du support Windows "natif" est pratiquement inchangé. Malheureusement, nous manquons toujours de l'expertise et de l'intérêt pour réaliser un tel soutien. Cependant, depuis la sortie de Windows 10, il semble qu'un environnement de compatibilité "Ubuntu pour Windows" ou bash soit disponible. C'est probablement une avenue beaucoup plus prometteuse pour obtenir la prise en charge de Windows. (Mais encore une fois, personne dans notre groupe ne développe ou n'utilise Windows, nous n'avons donc même pas examiné cette option non plus.)

Ok un dernier post pour le moment ...

@homocomputeris pour un benchmark comme celui-ci, cela aide vraiment à montrer une bibliothèque bien connue, comme OpenBLAS, car sinon nous n'avons aucune idée de la vitesse de votre matériel.

@fgvanzee En parlant du support natif des foulées, quelles restrictions avez-vous ces jours-ci sur les foulées? Doivent-ils être alignés, positifs, non négatifs, multiples exacts de la taille des données, ...? (Comme vous vous en souvenez peut-être, les tableaux numpy permettent des pas totalement arbitraires mesurés en octets.)

@fgvanzee "bash for Windows" équivaut en fait à exécuter une machine virtuelle Linux sur Windows - une machine virtuelle particulièrement rapide et transparente, mais ce n'est pas un environnement natif. La bonne nouvelle est que vous supportez déjà bash pour Windows :-), mais la mauvaise nouvelle est que ce n'est pas un substitut au support natif de Windows.

@njsmith Mes résultats sont plus ou moins les mêmes que dans l'article.
Dernier MKL, par exemple:

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.

Je tiens à noter que je n'ai aucune idée de comment compiler BLIS pour utiliser tout ce que mon processeur peut optimiser et multithread. Alors que MKL a des choses plus ou moins hors de la boîte.

@njsmith Merci pour cette mise à jour. Je conviens que rien ne vaut le support natif du système d'exploitation. Je conviens également que nous devons voir le benchmark fonctionner avec d'autres bibliothèques pour que nous puissions interpréter correctement les timings de @homocomputeris .

En parlant du support natif des foulées, quelles restrictions avez-vous ces jours-ci sur les foulées? Doivent-ils être alignés, positifs, non négatifs, multiples exacts de la taille des données, ...? (Comme vous vous en souvenez peut-être, les tableaux numpy permettent des pas totalement arbitraires mesurés en octets.)

@njsmith aligné? Non. Positif? Je pense que nous avons levé cette contrainte, mais cela n'a pas été testé en profondeur. Des multiples exacts du type de données? Oui, encore.

J'introduis @devinamatthews dans la discussion. Il y a des mois, je lui ai parlé de votre demande de sauts d'octets, et il avait quelques bons points / questions à l'époque dont je ne me souviens pas très bien. Devin, pouvez-vous vous rappeler vos inquiétudes à ce sujet et, si oui, les exprimer à Nathaniel? Merci.

@homocomputeris Pourriez-vous réexécuter le benchmark avec une valeur différente pour size ? Je me demande si la valeur utilisée par l'auteur (4096) étant une puissance de deux est un cas d'utilisation particulièrement mauvais pour BLIS, et pas particulièrement réaliste pour la plupart des applications de toute façon. Je suggère d'essayer 4000 (ou 3000 ou 2000) à la place.

@homocomputeris Et avez-vous dit que les résultats BLIS sont à thread unique, alors que les résultats MKL sont multi-thread?

FWIW, j'ai cherché à construire BLIS sur Windows il y a quelque temps. Le principal problème pour le moment est le système de construction. Il pourrait être possible de faire en sorte que mingw utilise clang pour produire un binaire compatible MSVC. Je n'ai jamais réussi à faire fonctionner ça avec le temps que j'ai pu y consacrer, mais cela semble possible.

Dans le code source réel, la situation n'est pas trop mauvaise. Récemment, ils sont même passés à l'utilisation de macros pour leurs noyaux d'assemblage, c'est donc un obstacle de plus à la prise en charge de Windows éliminé. Voir https://github.com/flame/blis/issues/220#issuecomment -397842370 et https://github.com/flame/blis/pull/224. Il semble que les fichiers source eux-mêmes soient à quelques macros / ifdefs de plus pour construire sur MSVC, mais c'est mon point de vue en tant qu'extérieur. Je ne sais pas non plus comment faire fonctionner les makefiles BLIS existants avec MSVC.

@insertinterestingnamehere Merci de votre participation, Ian. Vous avez raison de dire que les noyaux d'assemblage re-macroisés sont un pas de plus vers une compatibilité MSVC. Cependant, comme vous l'avez souligné, notre système de construction n'a certainement pas été conçu avec la prise en charge de Windows. De plus, MSVC prend-il encore en charge C99? Sinon, c'est un autre obstacle. (BLIS nécessite C99.)

Eh bien, j'ai donné l'exemple ci-dessus uniquement pour montrer que BLIS est comparable aux autres, c'est pourquoi je n'ai rien inclus de plus spécifique.

Mais comme vous le demandez: smiley:

  • Processeur Intel Core i5-6260U avec le dernier BIOS et tous les correctifs pour Spectre / Meltdown
  • Linux 4.17.3-1-ARCH
  • tout est compilé avec gcc 8.1.1 20180531
  • NumPy 1.15.0rc1
  • J'ai choisi un premier pour les dimensions de la matrice

Intel MKL 2018.3 limité à 2 threads (c'est-à-dire mes cœurs de processeur physiques):

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 compilé avec
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.

Donc, il semble que BLIS devrait certainement être pris en charge par NumPy au moins dans les systèmes Unix / POSIX / n'importe quoi, comme j'imagine que Windows utilise le cas comme `` ne le touchez pas si cela fonctionne ''
La seule chose que je ne sais pas, c'est la connexion entre MKL / BLIS et LAPACK / libFLAME. Intel affirme avoir beaucoup de choses optimisées en plus de BLAS, comme LAPACK, FFT, etc.

@fgvanzee Pourquoi une puissance de 2 est-elle mauvaise pour BLIS? C'est assez courant pour les méthodes de collocations si l'on veut la FFT la plus rapide.

Pour numpy et al., Il suffirait de gérer le bâtiment dans mingw / MSYS2 --- c'est ce que nous faisons actuellement avec openblas sous Windows (bien que ce soit une sorte de hack en soi). Cela limitera l'utilisation aux API "traditionnelles" qui n'impliquent pas de passer des ressources CRT, mais c'est très bien pour BLAS / LAPACK.

@fgvanzee bon point à propos de C99. WRT le préprocesseur C99, étonnamment, même le préprocesseur MSVC 2017 n'est pas complètement rattrapé. On suppose qu'ils sont en train de corriger cela (https://docs.microsoft.com/en-us/cpp/visual-cpp-language-conformance#note_D).

@fgvanzee @njsmith Voici ce que nous aurions besoin de faire pour prendre en charge les sauts d'octets arbitraires:

1) Modifiez l'interface. La chose la plus rapide qui me vient à l'esprit est d'ajouter quelque chose comme un drapeau stride_units dans l'interface objet.
2) Refactorisez tous les éléments internes pour n'utiliser que des pas d'octets. Ce n'est en aucun cas une mauvaise idée.
3) Lors de l'emballage, vérifiez l'alignement du type de données et si ce n'est pas le cas, utilisez le noyau d'emballage générique.
a) Le noyau d'empaquetage générique devrait également être mis à jour pour utiliser memcpy . Si nous pouvons le régler pour utiliser un paramètre de taille littéral, il ne devrait pas être horriblement nul.
4) Lorsque C n'est pas aligné, utilisez également un micro-noyau virtuel qui accède à C en utilisant memcpy .

Ceci est juste pour les matrices d'entrée et de sortie. Si alpha et beta peuvent être des pointeurs arbitraires, alors il y a plus de problèmes. Notez que sur x86, vous pouvez lire / écrire des données non alignées très bien, mais d'autres architectures (en particulier ARM) seraient un problème. Le compilateur peut également introduire des problèmes d'alignement supplémentaires lors de la vectorisation automatique .

@homocomputeris :

  1. Je ne voulais pas dire que les pouvoirs de deux n'apparaissent jamais «dans la nature», mais seulement qu'ils sont surreprésentés dans les benchmarks, probablement parce que nous, les humains orientés vers l'informatique, aimons compter en puissances de deux. :)
  2. Ces résultats de référence sont vraiment similaires. J'adorerais que la réponse à cette question suivante soit "non", mais est-il possible que vous ayez accidentellement exécuté les deux benchmarks avec MKL (ou BLIS) lié?
  3. Je suis tout à fait d'accord que les pouvoirs de deux se produisent dans les applications liées à la FFT. J'avais l'habitude de travailler dans le traitement du signal, donc je comprends. :)
  4. Mon inquiétude avec BLIS ne fonctionnant pas bien avec les puissances de deux n'est en fait pas une préoccupation propre à BLIS. Cependant, il se peut que le phénomène que nous observons soit plus prononcé avec BLIS, et donc une "pénalité" nette pour BLIS par rapport à une solution ridiculement optimisée telle que MKL. Le souci est le suivant: lorsque les matrices sont de dimension qui est une puissance de deux, il est probable que leur «dimension dominante» soit aussi une puissance de deux. (La dimension principale correspond au pas de colonne lorsque la matrice est stockée dans la colonne, ou au pas de ligne lorsqu'il est stocké en ligne.) Supposons un instant le stockage de ligne. Lorsque la dimension principale est une puissance de deux, la ligne de cache dans laquelle l'élément (i, j) réside dans le même ensemble d'associativité que la ligne de cache dans laquelle les éléments (i + 1, j), (i + 2, j) , (i + 3, j) etc live - c'est-à-dire les mêmes éléments des lignes suivantes. Cela signifie que lorsque l'opération gemm met à jour, par exemple, un microtile réel 6x8 double précision de C, ces 6 lignes correspondent toutes à la même associativité définie dans le cache L1, et inévitablement certaines d'entre elles sont expulsées avant d'être réutilisé. Ces soi-disant conflits manqués apparaîtront dans nos graphiques de performances comme des pics de performance occasionnels. Autant que je sache, il n'y a pas de moyen facile de contourner ce coup de performance. Nous emballons / copions déjà les matrices A et B, donc cela ne les affecte pas autant, mais nous ne pouvons pas emballer la matrice C dans une dimension de tête plus favorable sans prendre une énorme copie de mémoire. (Le remède serait pire que la maladie.) Maintenant, peut-être que MKL a un moyen d'atténuer cela, peut-être en passant à un micro-noyau de forme différente qui minimise le nombre de conflits manqués. Ou peut-être qu'ils ne le font pas, mais je sais que BLIS n'essaye pas de faire quoi que ce soit pour atténuer cela. Espérant que cela répond à votre question.
  5. Vous avez raison, MKL est bien plus qu'une simple fonctionnalité BLAS + LAPACK. Cependant, gardez à l'esprit que MKL est une solution de source fermée détenue dans le commerce. Bien qu'il soit disponible à des fins non commerciales «gratuitement», il n'y a aucune garantie qu'Intel ne rendra pas MKL indisponible au public à l'avenir, ni ne recommencera à le facturer. De plus, ce n'est pas vraiment génial pour nous, les informaticiens qui veulent comprendre l'implémentation, ou peaufiner ou modifier l'implémentation, ou fonder nos recherches sur des blocs de construction bien compris. Cela dit, si tout ce que vous voulez faire est de résoudre votre problème et de passer à autre chose, et que vous pouvez l'exprimer via BLAS, c'est génial. :)

@fgvanzee Mis à jour. Mon mauvais, pour une raison quelconque, il a été compilé avec MKL, même si j'avais mis BLIS en configuration.
SVD et EVD sont-ils implémentés dans LAPACK?

@homocomputeris Oui, LAPACK implémente SVD et EVD. Et je m'attends à ce que les implémentations de MKL soient extrêmement rapides.

Cependant, EVD est un peu ambigu: il y a le problème des valeurs propres généralisé et un EVD hermitien (ou symétrique). Il existe également une MVE tridiagonale, mais généralement très peu de gens s'y intéressent, à l'exception des personnes qui mettent en œuvre la MVE hermitienne.

NumPy abandonnera la prise en charge de Python 2.7 et 3.4 à la fin de 2018, ce qui permettra d'utiliser des compilateurs MSVC plus récents et compatibles C99, c'est l'une des raisons pour lesquelles nous faisons ce mouvement avant que le support officiel de 2.7 ne s'épuise. Voir http://tinyurl.com/yazmczel pour plus d'informations sur la prise en charge de C99 dans les compilateurs MSVC récents. Le support n'est pas complet (est-ce que quelqu'un est complet?) Mais peut être suffisant. Nous déplacerons NumPy lui-même vers C99 à un moment donné, comme certains (Intel) l'ont demandé pour l'amélioration du support numérique.

@charris Merci pour cette info, Charles. Il est probable que tout ce qui sera mis en œuvre sera suffisant, mais nous ne le saurons pas avec certitude avant de l'essayer.

Dans tous les cas, BLIS n'a pas besoin de battre MKL pour commencer à être largement adopté; son concurrent immédiat est OpenBLAS.

BLIS n'a pas besoin de battre MKL pour commencer à être largement adopté; son concurrent immédiat est OpenBLAS.

Je ne pourrais pas être plus d'accord. MKL est dans une ligue à part.

@njsmith Juste curieux: dans quelle mesure la communauté numpy serait-elle intéressée par les nouvelles API / fonctionnalités permettant d'exécuter, par exemple, gemm en précision mixte et / ou domaine mixte? Autrement dit, chaque opérande pourrait être d'un type de données différent, le calcul étant effectué avec (potentiellement) une précision différente de celle de A et B ou les deux?

@fgvanzee Intéressé, oui, bien que le nombre potentiel de combinaisons boggles. Mais pour le moment, nous faisons des conversions de type, ce qui prend du temps et consomme de la mémoire. J'ai également joué avec l'idée d'avoir des routines optimisées pour les types entiers, bien que boolean puisse être le seul type qui ne souffrirait pas irrémédiablement d'un débordement.

Et il y a toujours float16. Les gens de ML pourraient être plus intéressés par ces fonctionnalités. @GaelVaroquaux Pensées?

@charris Oui, le nombre de cas peut être décourageant. Il existe 128 combinaisons de types pour gemm , en supposant les quatre types de données à virgule flottante traditionnels, et ce nombre exclut toute inflation combinatoire des paramètres de transposition / conjugaison ainsi que des possibilités de stockage de la matrice (auquel cas il irait jusqu'à 55 296).

La mise en œuvre complète des 128 cas, même via une implémentation de référence à faible performance, serait remarquable, et le faire d'une manière plus performante en minimisant les frais généraux serait tout à fait spécial. (Et, grâce à la fondation basée sur les objets de BLIS, si nous devions l'implémenter, cela coûterait très peu en termes de code objet supplémentaire.)

Si cela vous intéresse, veuillez regarder notre projet dans les prochains jours / semaines. :)

La fourche BLIS est-elle sûre?

Dans tous les cas, BLIS n'a pas besoin de battre MKL pour commencer à être largement adopté; son concurrent immédiat est OpenBLAS.

En effet. Pour être honnête, je n'ai pas pu faire fonctionner NumPy + BLAS sur mon système, mais ils semblent avoir des performances très similaires, à en juger par l'article que j'ai cité précédemment.
Probablement, libFLAME peut accélérer les opérations LAPACK si elle est liée à NumPy.

Une autre question intéressante est de comparer le fork BLIS / libFLAME d'AMD sur leurs derniers processeurs Zen pour voir s'ils ont obtenu une amélioration. Cela devient encore plus intéressant à la lumière des processeurs Intel problématiques.

Sur Linux et Windows actuellement, les roues officielles de numpy incluent une copie pré-construite d'OpenBLAS, donc sur ces plates-formes, le moyen le plus rapide d'obtenir une copie est pip install numpy . (Bien sûr, ce n'est pas tout à fait juste parce que, par exemple, cette version est construite avec un compilateur différent de celui utilisé pour construire BLIS localement, mais cela peut donner une idée.)

La fourche BLIS est-elle sûre?

@jakirkham J'ai parlé à @devinamatthews à ce sujet, et il semble penser que la réponse est oui. BLIS génère des threads à la demande (par exemple, lorsque gemm est appelé) plutôt que de maintenir un pool de threads comme le fait OpenBLAS.

Nous sommes cependant curieux: de quel type de modèle de filetage numpy attend-il / préfère-t-il / dépend-il, le cas échéant? Avez-vous besoin d'un pool de threads pour réduire la surcharge dans le cas d'utilisation d'appels successifs de très petits problèmes? (Le threading à la demande de BLIS n'est pas bien adapté à ce type d'utilisation.)

EDIT: Dans la même veine, avez-vous besoin de pthreads, ou êtes-vous prêt à OpenMP?

Merci pour l'info.

Alors, utilise-t-il pthreads, OpenMP, les deux? FWIW, il existe des problèmes connus avec OpenMP de GCC et le fait qu'AFAIK ne soit pas résolu (cc @ogrisel ). Être aussi forking via le module multiprocessing en Python est l'une des stratégies de parallélisme les plus courantes, être capable d'utiliser des threads (de préférence pthreads pour la raison ci-dessus) de manière sûre est assez important dans Python en général. Bien qu'il y ait plus d'options ces jours-ci avec des choses comme loky par exemple qui utilisent fork-exec.

Certes, Nathaniel en saurait beaucoup plus que moi à ce sujet, mais depuis que j'ai déjà commencé à écrire ce commentaire, IIUC NumPy n'utilise pas le threading lui-même sauf via des bibliothèques externes (par exemple BLAS). Bien que NumPy publie assez souvent le GIL , ce qui permet au threading en Python d'être un peu efficace. Des choses comme Joblib, Dask, etc. profitent de cette stratégie.

En ce qui concerne les pools de threads, il est intéressant que vous posiez des questions à ce sujet car je venais de profiler une technique ML pour maximiser les performances l'autre jour qui exécute une série de routines BLAS en Python avec NumPy / SciPy. En utilisant OpenBLAS et un nombre raisonnable de cœurs, cette routine est capable de saturer les cœurs pour la longueur de la routine même s'il n'y a pas de thread explicite utilisé en Python (y compris NumPy et SciPy) simplement parce qu'OpenBLAS a utilisé un threadpool pour la longueur du routine. Alors oui, les pools de threads sont incroyablement précieux.

Sur un autre point, BLIS gère-t-il la détection d'architecture dynamique? C'est assez important pour la construction d'artefacts qui peuvent être construits une fois et déployés sur une variété de systèmes différents.

@jakirkham Merci pour vos commentaires. Je suppose que le coût observé de ne pas avoir de pool de threads dépendra de la taille des opérandes de la matrice que vous transmettez et de l'opération que vous effectuez. Je suppose que gemm est une opération typique que vous ciblez (veuillez corriger si je me trompe), mais quelle taille de problème considérez-vous comme typique? J'imagine que le coût du modèle de création / jointure de BLIS se manifesterait si vous faisiez à plusieurs reprises une multiplication de matrice où A, B et C étaient 40x40, mais peut-être pas autant pour 400x400 ou plus. (TBH, vous ne devriez probablement pas vous attendre à beaucoup d'accélération de la parallélisation des problèmes 40x40 pour commencer.)

Sur un autre point, BLIS gère-t-il la détection d'architecture dynamique? C'est assez important pour la construction d'artefacts qui peuvent être construits une fois et déployés sur une variété de systèmes différents.

Oui. Cette fonctionnalité a été mise en œuvre il n'y a pas si longtemps, dans la seconde moitié de 2017. BLIS peut désormais cibler les soi-disant «familles de configuration», avec la sous-configuration spécifique à utiliser choisie au moment de l'exécution via une heuristique (par exemple cpuid instruction). Des exemples de familles prises en charge sont intel64 , amd64 , x86_64 .

@fgvanzee Je pense en fait que les performances sur les petites matrices sont l'un des points faibles d'OpenBLAS, donc c'est un peu inquiétant si BLIS est à nouveau plus lent ... Les gens utilisent certainement numpy dans toutes sortes de situations, donc nous apprécions vraiment les bibliothèques qui "juste travailler" sans avoir besoin de réglage pour des cas spécifiques. (Je suppose que c'est un peu différent du paramètre classique d'algèbre linéaire dense, où les deux cas les plus importants sont l'auteur de l'algorithme exécutant des tests de performance et les experts exécutant des travaux de superordinateur d'une semaine avec des administrateurs système spécialisés.) Par exemple, les gens utilisent souvent numpy avec Matrices 3x3.

Dans certains cas, gérer cela de manière appropriée peut simplement être une question de remarquer que vous avez un problème trop petit et de sauter complètement le filetage. Pour une gemme 3x3, la chose optimale est probablement d'être aussi stupide que possible.

Cependant, ce genre d'ajustement (ou même le problème de pool de threads contre aucun pool de threads) pourrait être quelque chose que la communauté interviendrait et ferait si BLIS commençait à se déployer à grande échelle.

En fait, cela me rappelle: je parlais à quelqu'un la semaine dernière qui savait que dans sa bibliothèque, il voulait invoquer gemm single-threaded, car il gérait le threading à un niveau supérieur, et il était frustré que les bibliothèques blas standard soient le seul moyen de contrôler cela se fait via des paramètres globaux qui sont assez faciles à appeler depuis une bibliothèque aléatoire. L'API native de BLIS permet-elle à l'utilisateur de contrôler la configuration des threads appel par appel? Je suppose que oui, parce que l'IIRC vous n'avez aucune configuration globale, en dehors de la couche de compatibilité BLAS?

Je pense en fait que les performances sur les petites matrices sont l'un des points faibles d'OpenBLAS, donc c'est un peu inquiétant si BLIS est à nouveau plus lent ... Les gens utilisent certainement numpy dans toutes sortes de situations, donc nous apprécions vraiment les bibliothèques qui "juste travailler "sans avoir besoin de réglage pour des cas spécifiques.

Je comprends que vous vouliez que cela "fonctionne simplement", mais j'essaie d'être réaliste et honnête avec vous. Si vous avez un problème 3x3 et que les performances sont vraiment importantes pour vous, vous ne devriez probablement même pas utiliser BLIS. Je ne dis pas que BLIS fonctionnera 10 fois plus lentement qu'une triple boucle naïve pour 3x3, juste que ce n'est pas la taille du problème dans laquelle les implémentations de BLIS excellent.

Je suis surpris d'apprendre que vous n'êtes pas satisfait d'OpenBLAS pour les petits problèmes. Quel est votre point de référence? Est-ce simplement que les performances sont inférieures à celles des gros problèmes? Atteindre les performances les plus élevées possibles pour les petits problèmes nécessite une stratégie différente de celle des grands problèmes, c'est pourquoi la plupart des projets ciblent l'un ou l'autre, puis traitent simplement les cas non ciblés de manière sous-optimale.

Dans certains cas, gérer cela de manière appropriée peut simplement être une question de remarquer que vous avez un problème trop petit et de sauter complètement le filetage. Pour une gemme 3x3, la chose optimale est probablement d'être aussi stupide que possible.

Je conviens qu'une coupure est idéale. Mais déterminer où ce seuil devrait être n'est pas trivial et varie très certainement selon l'architecture (et entre les opérations de niveau 3). C'est donc très possible, mais cela n'a pas encore été implémenté (ou bien pensé).

Cependant, ce genre d'ajustement (ou même le problème de pool de threads contre aucun pool de threads) pourrait être quelque chose que la communauté interviendrait et ferait si BLIS commençait à se déployer à grande échelle.

Oui.

En fait, cela me rappelle: je parlais à quelqu'un la semaine dernière qui savait que dans sa bibliothèque, il voulait invoquer gemm single-threaded, car il gérait le threading à un niveau supérieur, et il était frustré que les bibliothèques blas standard soient le seul moyen de contrôler cela se fait via des paramètres globaux qui sont assez faciles à appeler depuis une bibliothèque aléatoire.

Séquentiel gemm partir d'une application multithread est l'un de nos cas d'utilisation préférés. (Rappel: si séquentiel gemm est son seul cas d'utilisation, il peut simplement configurer BLIS avec le multithreading désactivé. Mais supposons qu'il veuille construire une fois et ensuite décider du threading plus tard.)

L'API native de BLIS permet-elle à l'utilisateur de contrôler la configuration des threads appel par appel? Je suppose que oui, parce que l'IIRC vous n'avez aucune configuration globale, en dehors de la couche de compatibilité BLAS?

Même si vous configurez BLIS avec le multithreading activé, le parallélisme est désactivé (un thread) par défaut. C'est donc une deuxième façon de résoudre son problème.

Mais supposons qu'il veuille changer le degré de parallélisme au moment de l'exécution. Le parallélisme dans BLIS peut être défini lors de l'exécution. Cependant, cela implique toujours BLIS de définir et de lire en interne les variables d'environnement via setenv() et getenv() . (Voir le wiki Multithreading pour plus de détails.) Je ne suis donc pas sûr que ce soit un facteur décisif pour votre ami. Nous voulons implémenter une API où le threading peut être spécifié de manière plus programmatique (qui n'implique pas de variables d'environnement), mais nous n'en sommes pas encore tout à fait là. Il s'agit principalement de l'interface; l'infrastructure est en place. Une partie du problème est que nous avons tous été formés au fil des ans (par exemple OMP_NUM_THREADS , etc.) à spécifier un nombre lors de la mise en parallèle, et c'est une simplification excessive des informations que BLIS préfère; il y a cinq boucles dans notre algorithme gemm , dont quatre peuvent être parallélisées (cinq dans le futur). Nous pouvons deviner à partir d'un nombre, mais ce n'est généralement pas idéal car cela dépend de la topologie matérielle. Cela fait donc partie de ce qui freine les progrès sur ce front.

@devinamatthews Une chance d'ajouter TBLIS dans la même veine?

Si vous avez un problème 3x3 et que les performances sont vraiment importantes pour vous, vous ne devriez probablement même pas utiliser BLIS. Je ne dis pas que BLIS fonctionnera 10 fois plus lentement qu'une triple boucle naïve pour 3x3, juste que ce n'est pas la taille du problème dans laquelle les implémentations de BLIS excellent.

Je ne suis pas très inquiet d'obtenir des performances optimales pour les problèmes 3x3 (bien que ce soit évidemment bien si nous pouvons l'obtenir!). Mais pour donner un exemple extrême: si numpy est compilé avec BLIS comme bibliothèque d'algèbre linéaire sous-jacente et que certains utilisateurs écrivent a @ b dans leur code en utilisant numpy, j'espère certainement qu'il ne finira pas par fonctionner 10 fois plus lentement qu'une implémentation naïve. Attendre que les utilisateurs reconstruisent numpy avant de multiplier les matrices 3x3 est trop demander :-). D'autant plus que le même programme qui multiplie les matrices 3x3 à un endroit peut également multiplier les matrices 1000x1000 à un autre endroit.

Rappel: si le gemm séquentiel est son seul cas d'utilisation, il peut simplement configurer BLIS avec le multithreading désactivé. Mais supposons qu'il veuille construire une fois, puis décider du filetage plus tard.

Il expédie une bibliothèque Python. Il s'attend à ce que ses utilisateurs prennent sa bibliothèque et la combinent avec d'autres bibliothèques et leur propre code, le tout fonctionnant ensemble dans le même processus. Sa bibliothèque utilise GEMM, et il est probable que certains de ces autres codes qu'il ne contrôle pas - ou même dont il ne connaît pas - voudront également utiliser GEMM. Il veut pouvoir contrôler le threading de ses appels à GEMM, sans affecter accidentellement d'autres appels non liés à GEMM qui pourraient se produire dans le même processus. Et idéalement, il serait en mesure de le faire sans avoir à expédier sa propre copie de GEMM, car c'est très ennuyeux de le faire correctement, et c'est aussi un peu offensant sur le plan conceptuel qu'un programme doive inclure deux copies d'une grande bibliothèque juste vous pouvez donc obtenir deux copies d'une seule variable entière number_of_threads . Est-ce que ça fait plus de sens?

J'espère vraiment que cela ne finira pas par fonctionner 10 fois plus lentement qu'une implémentation naïve.

Maintenant que j'y pense, je ne serais pas surpris si c'était plusieurs fois plus lent que naïf pour des problèmes extrêmement petits (3x3), mais le point de croisement serait faible, peut-être aussi petit que 16x16. Et c'est un problème facile à résoudre. (Je voudrais le faire pour toutes les opérations de niveau 3.)

Il expédie une bibliothèque Python. Il s'attend à ce que ses utilisateurs prennent sa bibliothèque et la combinent avec d'autres bibliothèques et leur propre code, le tout fonctionnant ensemble dans le même processus. Sa bibliothèque utilise GEMM, et il est probable que certains de ces autres codes qu'il ne contrôle pas - ou même dont il ne connaît pas - voudront également utiliser GEMM. Il veut pouvoir contrôler le threading de ses appels à GEMM, sans affecter accidentellement d'autres appels non liés à GEMM qui pourraient se produire dans le même processus. Et idéalement, il serait en mesure de le faire sans avoir à expédier sa propre copie de GEMM, car c'est très ennuyeux de le faire correctement, et c'est aussi un peu offensant sur le plan conceptuel qu'un programme doive inclure deux copies d'une grande bibliothèque juste vous pouvez donc obtenir deux copies d'une seule variable entière number_of_threads. Est-ce que ça fait plus de sens?

Oui, cela a été utile - merci pour les détails. On dirait qu'il serait un candidat parfait pour notre API de threading encore inexistante. (Personnellement, je pense que la convention variable d'environnement est la folie de frontière et ont exprimé mes sentiments à mes collaborateurs sur plus d'une fois. Cela dit, il est assez facile à une large bande d'utilisateurs / référenceurs HPC, donc nous allons devoir avec une API d'exécution appropriée qui n'empêche pas l'utilisation de la variable d'environnement afin que tout le monde reste heureux.)

Pourriez-vous lui dire que c'est définitivement sur notre radar? Je vais me blottir avec Robert. Il sera peut-être assez intéressé pour approuver mon temps passé là-dessus le plus tôt possible. (L'API de threading est sur sa liste de souhaits depuis un certain temps.)

Construire BLIS sur Windows avec clang.exe ciblant MSVC ABI est certainement possible. J'ai passé quelques heures et voici les changements. Le nombre de changements requis était étonnamment faible par rapport à OpenBLAS.
Le journal est ici . Tous les tests BLIS et BLAS réussissent.

@isuruf Merci d'avoir pris le temps d'examiner cela. Je ne me considère pas qualifié pour évaluer si cela fonctionnera pour les gens engourdis (pour plus d'une raison), alors je m'en remettrai à eux en ce qui concerne la satisfaction de leurs besoins.

En ce qui concerne la pull request, j'ai quelques commentaires / demandes (tous assez mineurs), mais je vais commencer une conversation sur la pull request elle-même.

PS: Heureux que vous ayez pu trouver les erreurs f2c . J'ai importé juste assez de parties de libf2c dans la source du pilote de test BLAS (compilé comme libf2c.a ) pour que les choses soient liées, même si cela nécessitait un piratage dans les fichiers d'en-tête. (Je n'aime pas maintenir le code Fortran - au point que je préfère regarder f2c'ed C plutôt que Fortran - au cas où vous ne pourriez pas le dire.)

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.

Un mot sur le benchmark utilisé ci-dessus. Robert m'a rappelé qu'à moins de savoir comment ce benchmark (ou numpy?) Implémente Cholesky, EVD et SVD, les résultats de ces opérations ne seront pas si significatifs. Par exemple, si le code netlib LAPACK est utilisé pour la factorisation de Cholesky, la taille de bloc algorithmique sera erronée (loin d'être idéale). De plus, en fonction du nombre de liens vers MKL, cela peut déformer davantage les choses parce que MKL a sa propre implémentation de la factorisation Cholesky en plus de gemm rapide, donc il peut même ne pas être de pommes à pommes par rapport à MKL.

Je sais que vous l'avez peut-être utilisé simplement pour avoir une idée générale des performances, et c'est très bien. Comprenez simplement qu'il peut y avoir des démons cachés dans les détails qui ne permettent pas de l'utiliser pour autre chose que des comparaisons très approximatives.

En général, numpy diffère les opérations comme celles-ci à n'importe quelle bibliothèque LAPACK ou LAPACK-alike disponible. Les versions MKL utilisent presque certainement le diapason de LAPACK qu'Intel expédie dans MKL. Les builds BLIS utilisent probablement la référence LAPACK ou quelque chose comme ça.

Dans un sens, c'est juste: si vous essayez de choisir une configuration numpy pour faire du SVD rapide, alors il est pertinent que MKL ait une version optimisée disponible et BLIS ne le fait pas. (Je crois qu'OpenBLAS est entre les deux: ils livrent une version modifiée de LAPACK avec la bibliothèque, mais c'est beaucoup plus proche de l'implémentation de référence que la version de MKL.) Mais oui, c'est sûr si vous essayez de comprendre ce qu'est BLIS et pourquoi le les résultats ressemblent à ça, il est important de se rappeler qu'il y a bien plus que la simple efficacité GEMM dans SVD / etc.

Dans un sens, c'est juste:

Je comprends et je suis d'accord. C'est compliqué par le fait que certaines personnes utilisent des benchmarks comme celui proposé précédemment pour mesurer les performances absolues sur un seul matériel (c'est-à-dire pour juger de la qualité d'une implémentation par rapport aux performances de pointe), et certains l'utilisent pour comparer les implémentations. Je pense que les commentaires de Robert sur Cholesky et al. ciblent plus les premiers que les seconds.

@njsmith @insertinterestingnamehere @rgommers @charris Grâce au travail rapide d'Isuru, nous avons pu affiner et fusionner son support Windows basé sur Appveyor dans la branche master BLIS. Jetez un œil et dites-nous si et dans quelle mesure cela résout le problème de la prise en charge de Windows pour numpy.

@fgvanzee Pouvez-vous

Pouvez-vous créer un lien vers le PR?

Bien sûr, la voici .

Cette configuration initiale est excellente! Au moins en théorie, cela devrait être suffisant pour permettre de construire numpy avec BLIS sur Windows. Des choses comme la construction d'une dll ou la prise en charge de MinGW peuvent être ajoutées plus tard.

Une chose à noter à propos des dépendances utilisées dans ce cas: pthreads-win32 est LGPL. IDK, si quelque chose doit être fait à ce sujet.

L'octroi de licences est une question difficile. Je connais quelques grandes entreprises dont les politiques juridiques rendent difficile l'utilisation de licences "copyleft" comme LGPL alors que les licences "permissives" ne sont pas un problème. L'ajout de code LGPL à la base de code NumPy / SciPy ou aux roues serait certainement une source de préoccupation, à juste titre ou non.

Nous ne l'ajoutons pas à la base de code ici. L'expédition d'un composant LGPL dans les roues n'est pas un problème. Nous expédions maintenant une dll gfortran qui est GPL avec une exception d'exécution. Voir gh-8689

En effet. En outre, je ne serais pas surpris si ces entreprises préfèrent également MKL.

Nous devons simplement nous assurer que le composant LGPL n'est probablement pas lié statiquement.

@jakirkham Merci pour vos commentaires. Je suppose que le coût observé de ne pas avoir de pool de threads dépendra de la taille des opérandes de la matrice que vous transmettez et de l'opération que vous effectuez. Je suppose que gemm est une opération typique que vous ciblez (veuillez corriger si je me trompe), mais quelle taille de problème considérez-vous comme typique? J'imagine que le coût du modèle de création / jointure de BLIS se manifesterait si vous faisiez à plusieurs reprises une multiplication de matrice où A, B et C étaient 40x40, mais peut-être pas autant pour 400x400 ou plus. (TBH, vous ne devriez probablement pas vous attendre à beaucoup d'accélération de la parallélisation des problèmes 40x40 pour commencer.)

Principalement GEMM et SYRK sont typiques. GEMV apparaît parfois, mais souvent, cela est intégré dans une grande opération GEMM si possible.

Ce n'est pas atypique pour nous d'avoir au moins 1 dimension de l'ordre de 10 ^ 6 éléments. L'autre peut être compris entre 10 ^ 3 et 10 ^ 6. Cela varie donc un peu. Y a-t-il quelque chose que nous devrions faire pour que cela utilise le threadpool ou cela se produit-il automatiquement? Aussi comment se comporte le threadpool si le processus est forké?

Sur un autre point, BLIS gère-t-il la détection d'architecture dynamique? C'est assez important pour la construction d'artefacts qui peuvent être construits une fois et déployés sur une variété de systèmes différents.

Oui. Cette fonctionnalité a été implémentée il n'y a pas si longtemps, dans la seconde moitié de 2017. BLIS peut désormais cibler des «familles de configuration», avec la sous-configuration spécifique à utiliser choisie lors de l'exécution via une heuristique (par exemple, une instruction cpuid). Des exemples de familles prises en charge sont intel64, amd64, x86_64.

Pour commencer un peu, nous avons de vieux ordinateurs utilisant Nehalem tout au long de Broadwell. Peut-être que quelques machines personnelles plus récentes ont Kaby Lake. Il est donc important de pouvoir tirer le meilleur parti de l'architecture qui nous est fournie tout en ne nous bloquant pas si nous fonctionnons sur une machine ancienne en utilisant des éléments intrinsèques non pris en charge. Les sous-configurations de Flame prennent-elles en charge cette gamme. Y a-t-il des codes supplémentaires intégrés pour distribuer le noyau approprié sur différentes architectures? À quel point cela devient-il granulaire? Y a-t-il quelque chose que nous devons faire pendant la construction pour nous assurer que ces noyaux sont présents?

La construction de x86_64 vous permet d'obtenir:

  • Penryn (y compris Nehalem et toute autre puce Intel SSSE3 64 bits)
  • Pont de sable (y compris Ivy Bridge)
  • Haswell (+ Broadwell, Skylake, Kaby lac et le lac café, bien qu'il ne soit pas tout à fait optimale pour les trois derniers)
  • Xeon Phi (1ère et 2ème génération, nous pourrions aussi faire de la 3ème génération si besoin)
  • Skylake SP / X / W (sera probablement très proche de l'optimum sur Cannon Lake aussi)
  • Bulldozer / Piledriver / Steamroller / Excavatrice
  • Ryzen / EPYC

Notez que certains d'entre eux nécessitent un compilateur et / ou une version de binutils suffisamment récents, mais uniquement sur la machine de construction.

AFAIK libflame n'a pas de noyaux spécialisés pour aucune architecture, tout dépend de BLIS.

@jakirkham nous

AFAIK libflame n'a pas de noyaux spécialisés pour aucune architecture, tout dépend de BLIS.

C'est généralement correct. Bien que libflame ait quelques noyaux intrinsèques (actuellement uniquement SSE) pour appliquer les rotations de Givens, ces noyaux n'appartiennent pas à libflame et sont uniquement là parce que BLIS n'existait pas au moment où ils ont été écrits, et donc il n'y avait pas d'autre endroit pour les loger. Finalement, ces noyaux seront réécrits, mis à jour et déplacés dans BLIS.

Les sous-configurations de Flame prennent-elles en charge cette gamme. Y a-t-il des codes supplémentaires intégrés pour distribuer le noyau approprié sur différentes architectures? À quel point cela devient-il granulaire? Y a-t-il quelque chose que nous devons faire pendant la construction pour nous assurer que ces noyaux sont présents?

@jakirkham Je ne suis pas sûr de ce que vous entendez par "Flamme" dans ce cas. Nous avons deux produits: libflame et BLIS. Alors que libflame a besoin d'attention et probablement d'une réécriture, pratiquement toute mon attention au cours des dernières années a été sur BLIS.

Si vous remplacez textuellement «Flame» par «BLIS», la réponse est «oui, principalement».

À quel point cela devient-il granulaire?

Je ne sais pas ce que vous voulez dire, mais la prise en charge du noyau dans BLIS n'est pas aussi étendue que dans OpenBLAS. Par exemple, nous n'optimisons souvent pas le domaine complexe trsm . Les noyaux sont utilisés, dans une certaine combinaison, par des sous-configurations. Les sous-configurations sont choisies via cpuid . Vous obtenez exactement les noyaux "enregistrés" par la sous-configuration. Veuillez consulter le wiki de configuration pour plus de détails sur les écrous et les boulons.

Y a-t-il quelque chose que nous devons faire pendant la construction pour nous assurer que ces noyaux sont présents?

Si vous avez besoin d'une détection matérielle d'exécution, vous ciblez une famille de configuration (par exemple intel64 , x86_64 ) au moment de la configuration au lieu d'une sous-configuration spécifique (ou auto , qui sélectionne une sous-configuration spécifique). C'est ça.

Y a-t-il quelque chose que nous devrions faire pour que cela utilise le threadpool ou cela se produit-il automatiquement? Aussi comment se comporte le threadpool si le processus est forké?

Comme je l'ai déjà dit dans ce fil, BLIS n'utilise pas de pool de threads. Et je m'en remets à Devin pour la sécurité des fourches (et il semble penser que la fourche ne sera pas un problème).

Pour info, les packages BLIS conda sont disponibles pour linux, osx, windows sur conda-forge. (Actuellement en cours de construction de la branche de développement, en attente d'une version). Les packages ont été construits avec pthreads activés et configuration x86_64.

Il expédie une bibliothèque Python. Il s'attend à ce que ses utilisateurs prennent sa bibliothèque et la combinent avec d'autres bibliothèques et leur propre code, le tout fonctionnant ensemble dans le même processus. Sa bibliothèque utilise GEMM, et il est probable que certains de ces autres codes qu'il ne contrôle pas - ou même dont il ne connaît pas - voudront également utiliser GEMM. Il veut pouvoir contrôler le threading de ses appels à GEMM, sans affecter accidentellement d'autres appels non liés à GEMM qui pourraient se produire dans le même processus.

@njsmith J'ai parlé à Robert, et il est d'

@jakirkham L'un de nos contacts chez Intel, @jeffhammond , nous informe que les implémentations d'OpenMP utilisent régulièrement un modèle de pool de threads en interne. Ainsi, il nous décourage de mettre en œuvre des pools de threads de manière redondante dans BLIS.

Maintenant, il se peut, comme vous le suggérez, que numpy ait besoin / préfère les pthreads, auquel cas nous sommes peut-être de retour à un fork / join réel qui se passe sous l'API pthreads de style fork / join.

Alors, utilise-t-il pthreads, OpenMP, les deux?

Aussi, j'ai réalisé que j'avais oublié de répondre à cette question. Le parallélisme multithread de BLIS est configurable: il peut utiliser pthreads ou OpenMP. (Cependant, cela ne doit pas être confondu avec la dépendance d'exécution inconditionnelle de BLIS sur pthreads en raison de notre dépendance à pthread_once() , qui est utilisé pour l'initialisation de la bibliothèque.)

Malheureusement, à moins que les implémentations d'OpenMP (par exemple GOMP) ne deviennent généralement plus robustes au forking, ce n'est pas vraiment une option sûre en Python. Surtout pas dans quelque chose d'aussi bas dans la pile que NumPy.

Malheureusement, à moins que les implémentations d'OpenMP (par exemple GOMP) ne deviennent généralement plus robustes au forking, ce n'est pas vraiment une option sûre en Python. Surtout pas dans quelque chose d'aussi bas dans la pile que NumPy.

C'est suffisant. Il semble donc que numpy s'appuie sur l'option de configuration --enable-threading=pthreads pour le multithreading via BLIS.

Lorsqu'il est compilé avec pthreads, existe-t-il une API publique pour obtenir un contrôle par programme afin d'éviter les problèmes de surabonnement lors du parallélisme imbriqué avec des processus / pools de threads Python qui appellent numpy?

Plus précisément, voici les types de symboles publics que je rechercherais:

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

Similaire à ce qui est fait dans https://github.com/IntelPython/smp pour MKL et OpenMP.

Lorsqu'il est compilé avec pthreads, existe-t-il une API publique pour obtenir un contrôle par programme afin d'éviter les problèmes de surabonnement lors du parallélisme imbriqué avec des processus / pools de threads Python qui appellent numpy?

@ogrisel Excellente question, Olivier. Il existe un moyen de définir les paramètres de thread lors de l'exécution, mais c'est actuellement fait avec la sémantique globale. C'est sous-optimal parce que (en plus d'être global au lieu d'être sur une base par appel à gemm ou autre), il alimente des variables d'environnement.

Je travaille actuellement sur une refonte mineure du code sous-jacent qui permettra à quelqu'un d'utiliser l'une des sous-API dites "expertes" de BLIS pour passer une structure de données supplémentaire à gemm qui permettra le appelant pour spécifier sur une base par appel quelle devrait être la stratégie de parallélisation. La structure de données susmentionnée permettra aux utilisateurs de spécifier un seul nombre de threads et laissera BLIS faire automatiquement de son mieux pour déterminer où obtenir le parallélisme, ou un niveau de parallélisme pour chaque boucle dans l'algorithme de multiplication matricielle (comme BLIS le préfère).

Quoi qu'il en soit, je pense que cette nouvelle approche répondra à vos besoins. Je suis déjà à mi-chemin avec les changements, donc je pense que je ne suis qu'à une semaine environ de la mise en place de la nouvelle fonctionnalité sur github. S'il vous plaît laissez-moi savoir si ce plan ressemble à quelque chose qui fonctionnera pour vous, et / ou si vous avez d'autres préoccupations ou demandes sur ce sujet.

PS: J'ai jeté un œil au lien loky que vous avez fourni. Ces symboles sont tous configurés pour un paramètre global de threads. Ma solution proposée n'empêcherait pas de définir les choses globalement, mais elle est conçue pour que ce ne soit pas la seule option. Ainsi, quelqu'un qui souhaite utiliser un maximum de 8 cœurs / threads pourrait avoir une application engendrer 2 threads, et chacun de ceux-ci invoquera une instance de gemm qui obtient un parallélisme à 4 voies. Ou 3 threads d'application où deux d'entre eux appellent gemm avec un parallélisme bidirectionnel, le troisième utilisant un parallélisme quadruple. (Vous avez eu l'idée.)

il alimente des variables d'environnement.

Qu'est-ce que ça veut dire? Est-il possible d'utiliser des ctypes de Python pour reconfigurer la taille du pool de threads BLIS par défaut / global une fois qu'il a déjà été initialisé dans un processus Python spécifique?

il alimente des variables d'environnement.

Qu'est-ce que ça veut dire?

@ogrisel Ce que je veux dire par là, c'est que nous avons une petite API dans BLIS pour définir ou obtenir le nombre de threads, similaire à omp_get_num_threads() et omp_set_num_threads() dans OpenMP. Cependant, ces fonctions de l'API BLIS sont implémentées sous forme d'appels à getenv() et setenv() . Il s'agit simplement d'un artefact historique du fait que l'un des cas d'utilisation d'origine de BLIS était de définir une ou plusieurs variables d'environnement dans le shell (par exemple bash ) puis d' exécuter une application liée à BLIS, donc à la il était opportun de s'appuyer simplement sur cette approche des variables d'environnement; cela n'a jamais été censé être le moyen ultime et parfait de spécifier le nombre de threads pour le parallélisme.

Est-il possible d'utiliser des ctypes de Python pour reconfigurer la taille du pool de threads BLIS une fois qu'il a déjà été initialisé dans un processus Python spécifique?

BLIS n'utilise pas explicitement un pool de threads. Nous utilisons plutôt un modèle de création / jointure pour extraire le parallélisme via pthreads. Mais oui, une fois BLIS initialisé, l'application (ou la bibliothèque appelante) peut modifier le nombre de threads à l'exécution via un appel de fonction, du moins en principe. (Je ne sais pas si cela fonctionnerait en pratique , cependant, car je ne sais pas comment Python gérerait BLIS en essayant de définir / d'obtenir des variables d'environnement.) Mais comme je l'ai mentionné précédemment, une fois que j'ai effectué quelques modifications, l'application / La bibliothèque pourra spécifier un schéma de parallélisation personnalisé sur une base par appel au moment où l'opération de niveau 3 est appelée. Cela serait fait en initialisant d'abord un petit type struct données struct dans une version "experte" étendue de l'API BLIS pour gemm , par exemple.) rendrait les variables d'environnement inutiles pour ceux qui n'en ont pas besoin / qui n'en veulent pas. J'espère que cela répond à votre question.

Merci beaucoup pour les clarifications. L'API d'expert par appel est intéressante mais nécessiterait numpy de maintenir une API spécifique pour l'exposer à ses appelants de niveau Python. Je ne suis pas sûr que nous le souhaitons. Je pense que pour les utilisateurs numpy, avoir un moyen de changer la valeur actuelle du niveau de parallélisme global (au niveau du processus) est suffisant. Personnellement, cela ne me dérange pas si cela est réalisé en modifiant la valeur actuelle de la variable env tant que ce changement est pris en compte pour les appels BLAS-3 suivants.

@charris float16 pourrait en effet être intéressant pour certaines charges de travail d'apprentissage automatique, du moins au moment de la prédiction, même si je n'ai pas d'expérience personnelle avec cela.

Je pense que pour les utilisateurs numpy, avoir un moyen de changer la valeur actuelle du niveau de parallélisme global (au niveau du processus) est suffisant. Personnellement, cela ne me dérange pas si cela est réalisé en modifiant la valeur actuelle de la variable env tant que ce changement est pris en compte pour les appels BLAS-3 suivants.

Bon à savoir. Si tel est le cas, alors BLIS est bien plus proche d'être prêt à être utilisé par numpy. (Je voudrais simplement faire ces modifications en cours en premier, qui permettent également de corriger une condition de course auparavant inaperçue.)

Il vaut probablement mieux ne pas dépendre des variables d'environnement revérifiées à chaque appel, car getenv n'est pas très rapide, il pourrait donc être judicieux de le supprimer plus tard. (De plus, je ne pense pas que ce soit même garanti pour les threads?) Mais les appels d'API bli_thread_set_num_threads devraient convenir, car même si BLIS arrête d'appeler getenv tout le temps, l'API appelle peut être ajusté pour continuer à travailler malgré tout.

À plus long terme, je pense qu'il serait logique de commencer à exposer des API au-delà de BLAS dans numpy. L'une des choses qui rend BLIS attrayant en premier lieu est exactement qu'il fournit des fonctionnalités que les autres bibliothèques BLAS n'ont pas, comme la possibilité de multiplier les matrices strided, et il y a du travail en cours pour étendre les API BLAS de plusieurs façons .

Nous ne voudrions pas coder en dur les détails spécifiques à la bibliothèque dans l'API de numpy (par exemple, nous ne voudrions pas que np.matmul commence à prendre des arguments correspondant aux paramètres JC, IC, JR et IR de

Une chose que je n'ai pas vue mentionnée est la précision de l'index. La plupart des bibliothèques fournies par le système semblent utiliser des entiers 32 bits, ce qui est une limitation pour certaines applications de nos jours. À un moment donné, ce serait bien si tous les index étaient de 64 bits, ce qui nécessite probablement que nous fournissions la bibliothèque. Je ne sais pas ce que nous faisons actuellement en ce qui concerne la taille de l'indice. @ matthew-brett Compilons-nous toujours avec des entiers 32 bits?

@charris La taille entière dans BLIS est configurable au moment de la configuration: 32 ou 64 bits. En outre, vous pouvez configurer la taille entière utilisée dans l'API BLAS indépendamment de la taille entière interne.

En fait, cela me rappelle: je parlais à quelqu'un la semaine dernière qui savait que dans sa bibliothèque, il voulait invoquer gemm single-threaded, car il gérait le threading à un niveau supérieur, et il était frustré que les bibliothèques blas standard soient le seul moyen de contrôler cela se fait via des paramètres globaux qui sont assez faciles à appeler depuis une bibliothèque aléatoire.

@njsmith J'ai corrigé la condition de diriger votre ami vers documentation multithreading a également été mise à jour et guide le lecteur à travers ses choix, avec des exemples de base donnés. Le commit est sur la branche dev pour le moment, mais j'espère le fusionner avec master bientôt. (J'ai déjà mis le code à l'épreuve.)

EDIT: Liens mis à jour pour refléter la validation de correctif mineur.

Comme ajout possible aux types pris en charge, qu'en est-il de long double ? Je note que certaines architectures commencent à prendre en charge la précision quad (toujours dans le logiciel) et je pense qu'à un moment donné, la précision étendue sera remplacée par la précision quad sur Intel. Je ne pense pas que ce soit immédiatement urgent, mais je pense qu'après toutes ces années, les choses commencent à évoluer dans ce sens.

@charris Nous sommes dans les premiers stades de la prise en charge de bfloat16 et / ou float16 en particulier en raison de leurs applications d'apprentissage automatique / IA, mais nous sommes également conscients de la demande de double double et quad-précision. Nous aurions besoin de jeter les bases pour que cela soit réalisable pour l'ensemble du cadre, mais c'est certainement sur notre radar à moyen et long terme.

@charris Selon https://en.wikipedia.org/wiki/Long_double , long double peut signifier une variété de choses:

  • Type 80 bits implémenté en x87, utilisant un stockage 12B ou 16B
  • double précision avec MSVC
  • double double précision
  • quadruple précision
    Parce que la signification est ambiguë et dépend non seulement du matériel mais du compilateur utilisé, c'est un désastre total pour les bibliothèques, car l'ABI n'est pas bien définie.

Du point de vue des performances, je ne vois aucun avantage à float80 (ie x87 long double) car il n'y a pas de version SIMD. Si l'on peut écrire une version SIMD de double double dans BLIS, cela devrait mieux fonctionner.

L'implémentation de float128 dans le logiciel est au moins un ordre de grandeur plus lente que float64 dans le matériel. Il serait prudent d'écrire une nouvelle implémentation de float128 qui saute toute la gestion FPE et se prête à SIMD. L'implémentation dans libquadmath, bien que correcte, ne mérite pas l'attention d'une implémentation BLAS haute performance comme BLIS.

Oui, c'est un problème. Je ne pense pas que la précision étendue en vaille la peine, et le besoin de précision quad est irrégulier, le double est bon pour la plupart des choses, mais quand vous en avez besoin, vous en avez besoin. Je ne suis pas préoccupé par les performances, le besoin n'est pas de vitesse mais de précision. Notez que nous venons d'étendre le support à un ARM64 avec quad precision long double, une implémentation logicielle bien sûr, mais je m'attends à ce que le matériel suive à un moment donné et ce serait peut-être bien d'avoir quelque chose de testé et prêt à l'emploi.

La proposition BLAS G2 prend en compte les calculs double-double et "reproductibles" dans BLAS. (Reproductible ici signifie déterministe entre les implémentations, mais IIUC implique également l'utilisation de valeurs intermédiaires de plus haute précision.)

Excité de voir cela avancer!

Pour mémoire, je suis celui que @njsmith faisait référence à qui était intéressé par le contrôle du threading à partir du logiciel. Mes charges de travail sont étrangement parallèles au moment de la prédiction, et mes multiplications matricielles sont relativement petites. Je préfère donc paralléliser des unités de travail plus importantes.

J'ai travaillé il y a environ un an sur l'empaquetage de Blis pour PyPi et l'ajout de liaisons Cython: https://github.com/explosion/cython-blis

J'ai trouvé Blis assez facile à empaqueter en tant qu'extension C comme celle-ci. La principale pierre d'achoppement pour moi était la prise en charge de Windows. De mémoire, c'était des problèmes C99, mais je me souviens peut-être mal.

L'interface Cython que j'ai ajoutée pourrait être intéressante. En particulier, j'utilise les types fusionnés de Cython pour qu'il y ait une seule fonction nogil qui peut être appelée avec une vue mémoire ou un pointeur brut, pour les types float et double. Ajouter plus de branches pour plus de types n'est pas non plus un problème. Les types fusionnés sont essentiellement des modèles: ils permettent une exécution conditionnelle au moment de la compilation, sans surcharge.

Je serais très heureux de maintenir un package Blis autonome, de garder les roues construites, de maintenir une belle interface Cython, etc. Je pense que ce serait très bien de l'avoir en tant que package séparé, plutôt que quelque chose intégré dans numpy. Nous pourrions alors exposer davantage l'API de Blis, sans être limité par ce que les autres bibliothèques BLAS prennent en charge.

@honnibal Désolé pour le retard dans la réponse sur ce fil, Matthew.

Merci pour votre message. Nous sommes toujours heureux de voir que les autres sont enthousiasmés par BLIS. Bien sûr, nous serions heureux de vous conseiller chaque fois que cela est nécessaire si vous décidez de vous intégrer davantage dans l'écosystème python (une application, une bibliothèque, un module, etc.).

En ce qui concerne la prise en charge de Windows, veuillez consulter la prise en charge de clang / appveyor pour l'ABI Windows que @isuruf a récemment ajouté. La dernière fois que j'ai entendu parler de lui, cela fonctionnait comme prévu, mais nous ne faisons aucun développement sur Windows ici à UT, donc je ne peux pas garder un œil sur cela moi-même. (Bien qu'Isuru m'ait fait remarquer une fois que je pouvais m'inscrire à Appveyor d'une manière similaire à Travis CI.)

Veuillez également me faire savoir si vous avez des questions sur l'utilisation du thread par appel. (J'ai mis à jour notre documentation multithreading pour couvrir ce sujet.)

À partir de BLIS 0.5.2 , nous avons un document sur les performances qui présente les

Donc, si la communauté numpy se demande comment BLIS se compare aux autres solutions BLAS de premier plan, je vous invite à jeter un coup d'œil rapide!

Très intéressant, merci @fgvanzee.

J'ai dû chercher Epyc - il semble que ce soit un nom de marque basé sur l'architecture Zen (éventuellement mise à jour vers Zen + à un moment donné?). Peut-être préférable de renommer Zen? Pour notre base d'utilisateurs, Ryzen / Threadripper sont les marques les plus intéressantes, elles peuvent reconnaître Zen mais probablement pas Epyc.

Epyc est le nom de la ligne de serveurs AMD. C'est le successeur des produits AMD Opteron du passé.

Il n'y a malheureusement pas de moyen unique pour BLIS d'étiqueter ses cibles architecturales, car le code dépend du vecteur ISA (par exemple AVX2), de la microarchitecture du cœur du processeur (par exemple Ice Lake) et de l'intégration SOC / plateforme (par exemple, processeur Intel Xeon Platinum) ). BLIS utilise des noms de code de microarchitecture dans certains cas (par exemple Dunnington) mais ce n'est pas mieux pour tout le monde.

@fgvanzee Vous pourriez envisager d'ajouter les alias qui correspondent aux noms GCC march / mtune / mcpu ...

@rgommers La sous-configuration de BLIS qui couvre Ryzen et Epyc est déjà nommée zen , car elle capture les deux produits.

Quant à savoir si Ryzen / Threadripper ou Epyc sont des marques plus intéressantes (même pour les utilisateurs numpy), je dirai ceci: si je ne pouvais comparer qu'un seul système AMD Zen, ce serait l'Epyc le plus haut de gamme, car: (a) il utilise une microarchitecture similaire à celle de Ryzen; (b) cela me donne le maximum de 64 cœurs physiques (et, en prime, ces cœurs sont disposés dans une configuration quelque peu nouvelle, semblable à NUMA); qui (c) place une contrainte maximale sur BLIS et les autres implémentations. Et c'est essentiellement ce que nous avons fait ici.

Maintenant, heureusement, il n'y a pas de règle disant que je ne peux comparer qu'un seul système Zen. :) Cependant, il y a d'autres obstacles, en particulier en ce qui concerne l'accès en premier lieu. Je n'ai accès à aucun système Ryzen / Threadripper pour le moment. Si / quand j'y accède, je serai heureux de répéter les expériences et de publier les résultats en conséquence.

Jeff souligne certains des écueils de dénomination auxquels nous sommes confrontés. Généralement, nous nommons nos sous-configurations et ensembles de noyaux en termes de microarchitecture, mais il y a encore plus de nuances. Par exemple, nous utilisons notre sous-configuration haswell sur Haswell, Broadwell, Skylake, Kaby Lake et Coffee Lake. C'est parce qu'ils partagent tous fondamentalement le même vecteur ISA, qui est à peu près tout le code du noyau BLIS se soucie. Mais c'est un détail de mise en œuvre dont presque aucun utilisateur n'a besoin de se préoccuper. Si vous utilisez ./configure auto , vous obtiendrez presque toujours la meilleure sous-configuration et le meilleur jeu de noyau pour votre système, qu'ils soient nommés zen ou haswell ou autre. Pour l'instant, vous devez toujours adopter une approche plus pratique lorsqu'il s'agit de choisir de manière optimale votre schéma de threading, et c'est là que l'intégration SoC / plate-forme mentionnée par Jeff entre en jeu.

@jeffhammond Merci pour votre suggestion. J'ai envisagé d'ajouter ces alias dans le passé. Cependant, je ne suis pas convaincu que cela en vaille la peine. Cela ajoutera un encombrement important au registre de configuration, et les personnes qui l'examineront en premier lieu connaissent probablement déjà notre schéma de nommage pour les sous-configurations et les ensembles de noyau, et ne seront donc pas déroutés par l'absence de certaines révisions microarchitecturales noms dans ce fichier (ou dans le répertoire config ). Maintenant, si BLIS exigeait une identification manuelle de la sous-configuration, via ./configure haswell par exemple, alors je pense que les échelles penchent définitivement en faveur de votre proposition. Mais ./configure auto fonctionne assez bien, donc je n'en vois pas le besoin pour le moment. (Si vous le souhaitez, vous pouvez ouvrir un numéro sur ce sujet afin que nous puissions lancer une discussion plus large entre les membres de la communauté. Je suis toujours disposé à changer d'avis si la demande est suffisante.)

oui, nommer est toujours compliqué :) merci pour les réponses @fgvanzee et @jeffhammond

# 13132 et # 13158 sont liés

La discussion s'est un peu emportée; Quels sont les problèmes restants à résoudre pour prendre officiellement en charge BLIS dans numpy?

Naïvement, j'ai essayé de lancer des tests numpy avec BLIS depuis conda-forge (cf https://github.com/numpy/numpy/issues/14180#issuecomment-525292558) et pour moi, sous Linux, tous les tests ont réussi (mais peut-être que je a manqué quelque chose).

J'ai également essayé d'exécuter la suite de tests de test scipy dans le même env et il y a un certain nombre d'échecs dans scipy.linalg (cf https://github.com/scipy/scipy/issues/10744) au cas où quelqu'un aurait des commentaires sur cette.

Notez que BLIS de conda-forge utilise ReferenceLAPACK (netlib) comme implémentation LAPACK qui utilise BLIS comme implémentation BLAS et non libflame .

À propos de l'option BLIS sur conda-forge, ai-je raison de dire qu'elle est prête à l'emploi (contrairement à l'option OpenBLAS)?

Il vaut probablement mieux déplacer les discussions conda-forge vers conda-forge 🙂

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

Levstyle picture Levstyle  ·  3Commentaires

astrofrog picture astrofrog  ·  4Commentaires

inducer picture inducer  ·  3Commentaires

kevinzhai80 picture kevinzhai80  ·  4Commentaires

amuresan picture amuresan  ·  4Commentaires