C for Metal - métal précieux pour l'informatique sur les cartes graphiques Intel

Combien de cœurs de processeur Intel avez-vous sur votre ordinateur? Si vous utilisez un système basé sur Intel, dans la grande majorité des cas, vous devrez en ajouter un à votre réponse. Presque tous les processeurs Intel - d'Atom à Xeon E3, bien sûr, sans manquer le Core, incluent depuis de nombreuses années le noyau graphique intégré Intel Graphics, qui est essentiellement un processeur à part entière et, par conséquent, capable non seulement d'afficher des images à l'écran et d'accélérer la vidéo, mais aussi effectuer des calculs à usage général "ordinaires". Comment cela peut-il être utilisé efficacement? Regardez sous la coupe.



Tout d'abord, nous expliquerons brièvement pourquoi le recours à un processeur graphique Intel en vaut la peine. Bien sûr, les performances du processeur dans le système dépassent presque toujours de manière significative le GPU, car il s'agit également du processeur central.

Mais il est intéressant de noter que les performances des processeurs graphiques intégrés Intel au cours de la dernière décennie ont augmenté en pourcentage beaucoup plus que celles du processeur, et cette tendance se poursuivra certainement avec l'avènement de nouvelles cartes graphiques Intel discrètes. De plus, le GPU, de par son architecture (de nombreux dispositifs d'exécution vectorielle), est beaucoup mieux adapté à l'exécution d'un certain type de tâches - le traitement d'images, c'est-à-dire, en fait, à effectuer toutes opérations du même type sur des tableaux de données. Le GPU le fait avec une parallélisation interne complète, y dépense moins d'énergie que le CPU et, dans certains cas, le dépasse même en vitesse absolue. Enfin, le GPU et le CPU peuvent fonctionner en parallèle, chacun sur ses propres tâches, offrant des performances maximales et / ou une consommation d'énergie minimale de l'ensemble du système.

- D'accord, Intel. Nous avons décidé d'utiliser le GPU Intel pour les calculs à usage général, comment le faire?
- La manière la plus simple qui ne nécessite aucune connaissance particulière en graphisme (shaders Direct3D et OpenGL) est OpenCL.

Les noyaux OpenCL sont indépendants de la plate-forme et s'exécuteront automatiquement sur tous les périphériques informatiques disponibles dans le système - CPU, GPU, FPGA, etc. Mais le prix d'une telle polyvalence est loin des performances maximales possibles sur chaque type d'appareil, et notamment sur le GPU Intel intégré. Ici, nous pouvons donner un exemple: lors de l'exécution de code sur n'importe quel GPU Intel qui transpose une matrice 16x16 octets, l'avantage de performance de la programmation directe du GPU Intel sera 8 fois plus élevé qu'avec la version OpenCL!

De plus, certaines des fonctionnalités requises pour implémenter des algorithmes courants (par exemple, des "filtres larges" qui utilisent des données provenant d'un grand groupe de pixels dans une seule transformation), OpenCL ne le prend tout simplement pas en charge.

Par conséquent, si vous avez besoin d'une vitesse maximale sur le GPU et / ou de quelque chose de plus compliqué que de travailler indépendamment avec chaque élément de la matrice et ses voisins les plus proches, alors Intel C for Metal (ICM), un outil pour développer des applications fonctionnant sur Intel Graphics, vous aidera .

ICM - bienvenue dans la forge!


En termes de performances et de fonctionnalités, ICM peut être considéré comme «assembleur pour cartes graphiques Intel», et en termes de circuits et de convivialité - «un analogue d'OpenCL pour les cartes graphiques Intel».

Pendant de nombreuses années, ICM a été utilisé en interne par Intel dans le développement de produits de traitement multimédia sur le GPU Intel. Mais en 2018, ICM a été rendu public, et même en open source!

Intel C for Metal a obtenu son nom actuel il y a quelques mois, avant qu'il s'appelait Intel C for Media (le même acronyme ICM ou simplement CM ou même Cm), et plus tôt encore - Media Development Framework (MDF). Donc, si quelque part dans le nom du composant, dans la documentation ou dans les commentaires open source, les anciens noms se rencontrent - ne vous inquiétez pas, c'est une valeur historique.

Ainsi, le code d'application ICM, tout comme dans OpenCL, contient deux parties: celle «administrative», exécutée sur le processeur, et le noyau, exécuté sur le GPU. Sans surprise, la première partie est appelée l'hôte, et la seconde est le noyau.

Les noyaux sont une fonction du traitement d'un bloc de pixels donné (ou simplement des données), sont écrits dans le langage Intel C for Metal et compilés dans le jeu d'instructions GPU Intel (ISA) à l'aide du compilateur ICM.

L'hôte est une sorte de «gestionnaire d'équipe du noyau», il gère le processus de transfert de données entre le processeur et le GPU et effectue d'autres «travaux de gestion» via la bibliothèque d'exécution ICM Runtime et le pilote de média Intel GPU.
Un flux de travail ICM détaillé ressemble à ceci:


  • Le code hôte ICM est compilé par n'importe quel compilateur x86 C / C ++ avec l'application entière;
  • Le code du noyau ICM est compilé par le compilateur ICM dans un fichier binaire avec un ensemble d'instructions communes (Common ISA);
  • Au moment de l'exécution, cet ensemble général d'instructions JIT se traduit par un processeur graphique Intel spécifique;
  • L'hôte ICM appelle la bibliothèque d'exécution ICM pour communiquer avec le GPU et le système d'exploitation.

Quelques points plus importants et utiles:

  • Les surfaces utilisées dans ICM pour représenter / stocker des données peuvent être partagées avec DirectX 11 et 9 (DXVA sur Linux).
  • Le GPU peut prendre et écrire des données de la mémoire vidéo et de la mémoire système partagées avec le CPU. ICM comprend des fonctions spéciales pour les deux cas de transfert de données dans les deux sens. Dans le même temps, la mémoire système est exactement partagée et aucune copie réelle n'est requise - pour cela, la copie dite zéro est fournie dans ICM.

ICM - dans l'évent du volcan!


Déjà du nom «C for Iron» lui-même, il s'ensuit que la langue de l'appareil correspond à l'appareil graphique interne Intel. Autrement dit, il prend en compte le fait que le code sera exécuté sur plusieurs dizaines d'unités d'exécution de la carte graphique, dont chacune est un processeur entièrement vectoriel capable d'exécuter plusieurs threads simultanément.

Le langage ICM lui-même est C ++ avec quelques limitations et extensions. Par rapport à C ++, ICM manque ... de pointeurs, d'allocation de mémoire et de variables statiques. En vertu de l'interdiction également des fonctions récursives. Mais il existe une programmation par modèle vectoriel explicite (SIMD): types de données vectorielles - vecteur, matrice et surface; opérations vectorielles sur ces types de données, conditions vectorielles si / sinon, exécutées indépendamment pour chaque élément du vecteur; ainsi que des fonctions intégrées pour accéder aux fonctionnalités fixes du matériel Intel GPU.

Le travail avec des vecteurs, des matrices et des surfaces dans des tâches réelles est facilité par des objets de «sous-ensembles» - parmi les objets de base correspondants, vous ne pouvez choisir que les blocs «de référence» qui vous intéressent ou, comme cas spécial, des éléments individuels par masque.

Par exemple, regardons le code ICM qui implémente un filtre linéaire - remplacement d'une valeur
Couleurs RVB de chaque pixel par sa valeur moyenne et 8 voisins dans l'image:
I (x, y) = [I (x-1, y-1) + I (x-1, y) + I (x-1, y + 1) + I (x, y-1) +
+ I (x, y) + I (x, y + 1) + I (x + 1, y-1) + I (x + 1, y) + I (x + 1, y + 1)] / 9

Si les couleurs (données) dans la matrice sont situées comme R8G8B8 , alors le calcul avec la division de l'image d'entrée en blocs de 6x8 pixels (éléments de données 6x24 octets) sera le suivant:

_GENX_MAIN_ void linear(SurfaceIndex inBuf, SurfaceIndex outBuf, uint h_pos, uint v_pos){ //    8x32 matrix<uchar, 8, 32> in; //   6x24 matrix<uchar, 6, 24> out; matrix<float, 6, 24> m; //    read(inBuf h_pos*24, v_pos*6, in); //    -  m = in.select<6,1,24,1>(1,3); m += in.select<6,1,24,1>(0,0); m += in.select<6,1,24,1>(0,3); m += in.select<6,1,24,1>(0,6); m += in.select<6,1,24,1>(1,0); m += in.select<6,1,24,1>(1,6); m += in.select<6,1,24,1>(2,0); m += in.select<6,1,24,1>(2,3); m += in.select<6,1,24,1>(2,6); //  -   9   * 0.111f; out = m * 0.111f; //   write(outBuf, h_pos*24, v_pos*6, out); } 

  • La taille des matrices est définie sous la forme <type de données, hauteur, largeur>;
  • l' opérateur select <v_size, v_stride, h_size, h_stride> (i, j) renvoie la sous-matrice en commençant par l'élément (i, j) , v_size affiche le nombre de lignes sélectionnées, v_stride - la distance entre les lignes sélectionnées h_size - le nombre de colonnes sélectionnées, h_stride - la distance entre elles .

Veuillez noter que la taille de la matrice d'entrée 8x32 est choisie car bien que le bloc 8x30 soit algorithmiquement suffisant pour calculer les valeurs de tous les pixels du bloc 6x24, le bloc de données est lu en ICM non pas en octets, mais en éléments dword 32 bits.

Le code ci-dessus est en fait un noyau ICM à part entière. Comme mentionné, il sera compilé par le compilateur ICM en deux étapes (précompilation et traduction JIT ultérieure). Le compilateur ICM est construit sur la base de LLVM et, si vous le souhaitez, peut être étudié dans les sources et construit par vous-même .

Mais que fait l'hôte ICM? Appelle les fonctions de bibliothèque d'exécution ICM Runtime qui:

  • Créer, initialiser et supprimer après avoir utilisé le périphérique GPU (CmDevice), ainsi que les surfaces contenant des données utilisateur utilisées dans les noyaux (CmSurface);
  • Travailler avec des noyaux - téléchargez-les à partir de fichiers .isa précompilés, préparez leurs arguments, en indiquant la partie des données avec laquelle chaque noyau fonctionnera;
  • Créer et gérer la file d'attente d'exécution du noyau;
  • Ils contrôlent le fonctionnement des threads exécutant chaque noyau sur le GPU;
  • Gérer les événements (CmEvent) - objets de synchronisation du GPU et du CPU;
  • Transférer des données entre le GPU et le CPU, ou plutôt, entre le système et la mémoire vidéo;
  • Signaler les erreurs, mesurer le temps de fonctionnement des noyaux.

Le code hôte le plus simple ressemble à ceci:

 //  CmDevice cm_result_check(::CreateCmDevice(p_cm_device, version)); //  hello_world_genx.isa std::string isa_code = isa::loadFile("hello_world_genx.isa"); //    isa  CmProgram CmProgram *p_program = nullptr; cm_result_check(p_cm_device->LoadProgram(const_cast<char* >(isa_code.data()),isa_code.size(), p_program)); //  hello_world . CmKernel *p_kernel = nullptr; cm_result_check(p_cm_device->CreateKernel(p_program, "hello_world", p_kernel)); //       CmKernel CmThreadSpace *p_thread_space = nullptr; cm_result_check(p_cm_device->CreateThreadSpace(thread_width, thread_height, p_thread_space)); //   . cm_result_check(p_kernel->SetKernelArg(0, sizeof(thread_width), &thread_width)); //  CmTask –      //         //     . CmTask *p_task = nullptr; cm_result_check(p_cm_device->CreateTask(p_task)); cm_result_check(p_task->AddKernel(p_kernel)); //   CmQueue *p_queue = nullptr; cm_result_check(p_cm_device->CreateQueue(p_queue)); //    GPU (    ). CmEvent *p_event = nullptr; cm_result_check(p_queue->Enqueue(p_task, p_event, p_thread_space)); //   . cm_result_check(p_event->WaitForTaskFinished()); 

Comme vous pouvez le voir, il n'y a rien de compliqué à créer et à utiliser des noyaux et un hôte. Tout est simple!

La seule difficulté à mettre en garde pour retourner dans le monde réel: actuellement dans la version publique d'ICM, le seul moyen de déboguer les noyaux est les messages printf. Comment les utiliser correctement peut être vu dans l'exemple Hello, World .

ICM - pas du heavy metal!


Voyons maintenant comment cela fonctionne dans la pratique. Le kit de développement ICM est disponible pour Windows et Linux , et pour les deux systèmes d'exploitation contient le compilateur ICM, la documentation et les cas d'utilisation du didacticiel. Une description détaillée de ces exemples de formation est téléchargée séparément .

Pour Linux, le package comprend également un pilote de média en mode utilisateur pour VAAPI avec une bibliothèque d'exécution ICM Runtime intégrée. Pour Windows, le pilote graphique Intel habituel pour Windows fonctionnera avec ICM. La bibliothèque d'exécution ICM Runtime est incluse dans l'ensemble dll de ce pilote. Le package ICM inclut uniquement le fichier de lien .lib correspondant. Si le pilote est absent de votre système pour une raison quelconque, il est téléchargé sur le site Web d'Intel et le bon fonctionnement d'ICM dans les pilotes est garanti, à partir de la version 15.60 - 2017).

Le code source des composants peut être trouvé ici:


Le contenu supplémentaire de cette section s'applique exclusivement à Windows, mais les principes généraux de travail avec ICM sont également applicables à Linux.

Pour le travail «normal» avec le package ICM, vous aurez besoin de Visual Studio à partir de 2015 et de Cmake à partir de la version 3.2. Dans le même temps, les fichiers de configuration et de script des exemples de formation sont conçus pour VS 2015. Pour utiliser des versions plus récentes des fichiers VS, vous devrez étudier et modifier vous-même les chemins d'accès aux composants VS.

Alors, découvrez ICM pour Windows:

  • Téléchargez l'archive ;
  • Déballez-le;
  • Nous démarrons (de préférence sur la ligne de commande VS) le script de configuration de l'environnement setupenv.bat avec trois paramètres - la génération de GPU Intel (correspondant au processeur dans lequel le GPU est intégré, il peut être laissé par défaut: gen9), la plateforme de compilation: x86 \ x64 et la version DirectX pour partage avec ICM: dx9 / dx11.

Après cela, vous pouvez simplement créer tous les exemples de formation - dans le dossier des exemples, le script build_all.bat le fera ou générera des projets pour Microsoft Visual Studio - cela créera le script create_vs.bat avec le nom d'un exemple spécifique comme paramètre.

Comme vous pouvez le voir, l'application ICM sera un fichier .exe avec la partie hôte et un fichier .isa avec la partie GPU précompilée correspondante.

Divers exemples sont inclus dans le package ICM - du plus simple Hello, World, qui montre les principes de base du fonctionnement ICM, à celui assez compliqué - la mise en œuvre de l'algorithme pour trouver le "débit maximal - coupe minimale" du graphique (problème de débit minimal de débit maximal) utilisé dans la segmentation et l'assemblage d'images .

Toutes les études de cas ICM sont bien documentées dans le code et dans la description séparée déjà mentionnée. Il est recommandé de se plonger dans ICM précisément dessus - en étudiant et en exécutant séquentiellement des exemples, puis - en les modifiant pour répondre à vos besoins.

Pour une compréhension générale de toutes les fonctionnalités ICM existantes, il est fortement recommandé d'étudier la «spécification» - la description ICM cmlangspec.html dans le dossier \ documents \ compiler \ html \ cmlangspec .

En particulier, il décrit l'API des fonctions ICM implémentées dans le matériel - accès aux soi-disant échantillonneurs de texture (Sampler) - un mécanisme pour filtrer les images de différents formats, ainsi que pour évaluer le mouvement (Motion Estimation) entre les images vidéo et certaines capacités d'analyse vidéo.

ICM - frappez pendant qu'il fait chaud!


En ce qui concerne les performances des applications ICM, il convient de noter que les études de cas incluent la mesure du temps de leur travail, de sorte qu'en les exécutant sur le système cible et en les comparant avec vos tâches, vous pouvez évaluer la pertinence d'utiliser ICM pour elles.

Et les considérations générales concernant les performances ICM sont assez simples:

  • Lors du déchargement des calculs sur un GPU, rappelez-vous la surcharge de transfert des données du processeur <-> GPU et de la synchronisation de ces périphériques. Par conséquent, un exemple tel que Hello, World n'est pas un bon candidat pour une implémentation ICM. Mais les algorithmes de la vision par ordinateur, de l'IA et de tout traitement non trivial des tableaux de données, en particulier avec un changement de l'ordre de ces données dans le processus ou à la sortie, sont ce dont ICM a besoin.
  • De plus, lors de la conception d'un code ICM, il est nécessaire de prendre en compte le périphérique GPU interne, c'est-à-dire qu'il est conseillé de créer un nombre suffisant (> 1000) de threads GPU et de les charger tous avec du travail. Dans ce cas, c'est une bonne idée de diviser les images à traiter en petits blocs. Mais la manière spécifique de partitionner, ainsi que le choix d'un algorithme de traitement spécifique pour atteindre des performances maximales, n'est pas une tâche triviale. Cependant, cela s'applique à n'importe quelle façon de travailler avec n'importe quel GPU (et CPU).

Avez-vous du code OpenCL, mais ses performances ne vous plaisent pas? Ou du code CUDA, mais vous voulez travailler sur un nombre beaucoup plus grand de plateformes? Ensuite, il vaut la peine de jeter un œil à ICM.

ICM est un produit vivant et évolutif. Vous pouvez participer à son utilisation et à son développement - les référentiels correspondants sur github attendent vos validations. Toutes les informations nécessaires aux deux processus se trouvent dans cet article et les fichiers Lisez-moi sur github. Et si quelque chose manque, il apparaîtra après vos questions dans les commentaires.

Source: https://habr.com/ru/post/fr466521/


All Articles