Le Standard Ouvert OpenHMPP (HMPP pour Hybrid Multicore Parallel Programming) est un modèle de programmation basé sur un jeu de directives, conçu pour manipuler les accélérateurs matériels sans se soucier de la complexité associée à la programmation GPU. Le choix d’implémentation concernant ce modèle s’est porté sur les directives car elles permettent une relation déliée entre le code de l’application et l’utilisation des accélérateurs matériels.

NB : Cet article porte sur les directives OpenHMPP qui constituent l'Open Standard, mais ne traite pas de l'exécution des directives, liée à l'implémentation des directives.

Introduction

modifier

La syntaxe offerte par le modèle OpenHMPP permet de répartir efficacement les calculs sur les accélérateurs matériels et d’optimiser les mouvements de données de/vers la mémoire du matériel.

Le modèle est basé sur le projet CAPS (Compiler and Architecture for Embedded and Superscalar Processors) mené en commun par l'INRIA, le CNRS, l'Université de Rennes 1 et l'INSA de Rennes.

Le concept du standard OpenHMPP

modifier

Le standard OpenHMPP est basé sur le concept de codelet, qui est une fonction qui peut être exécutée à distance sur le matériel.

Le concept de codelet

modifier

Un codelet a les propriétés suivantes :

  1. C'est une fonction pure.
    • Il ne contient pas de déclarations de variables statiques ou volatiles et ne fait aucune référence à des variables globales, exception faite des variables déclarées comme "resident" par une directive OpenHMPP.
    • il ne contient pas d'appels à des fonctions avec un contenu non visible (c'est-à-dire qu'il ne peut pas être inliné). Cela inclut l'utilisation de librairies et de fonctions systèmes telles que malloc, printf…
    • Chaque appel à la fonction doit faire référence à une fonction statique pure (pas de pointeurs de fonction).
  2. Il ne retourne aucune valeur (l'équivalent en C est une fonction void; l'équivalent en FORTRAN est une subroutine).
  3. Le nombre d'arguments doit être fixé (pas de vararg comme en C).
  4. Il n'est pas récursif.
  5. Ces paramètres sont supposés ne pas être aliasés (au sens pointeur du terme).
  6. Il ne contient pas de directives callsite (c'est-à-dire pas d'appel RPC à un autre codelet) ou d'autres directives HMPP.

Ces propriétés permettent de s'assurer qu'un codelet RPC peut être exécuté à distance par un matériel. Cet appel RPC et ses transferts de données associés peuvent être asynchrones.

L'exécution RPC d'un codelet

modifier

OpenHMPP fournit un protocole d'appel de procédures à distance synchrone et asynchrone.

L'implémentation d'opération asynchrone est dépendante du matériel.

 
Synchronous versus asynchronous RPC

Le modèle de mémoire mis en œuvre

modifier

OpenHMPP prend en compte deux espaces d'adressage :

  • celui du processeur hôte
  • celui de la mémoire du matériel.
 
HMPP memory Model

Les directives HMPP

modifier

Les directives OpenHMPP peuvent être vues comme des “méta-informations” que l’on ajoute dans le code source de l’application. Ce sont donc des méta-informations inoffensives, c’est-à-dire qu’elle ne changent pas le comportement original du code.

Elles adressent l’exécution à distance (RPC) de la fonction ainsi que les transferts de données de/vers la mémoire du matériel. Le tableau qui suit introduit les directives OpenHMPP, classées selon le besoin adressé : certaines sont destinées à des déclarations, d’autres sont destinées à la gestion de l’exécution.

Directives HMPP
Instructions de contrôle de flux Directives pour la gestion des données
Déclarations codelet group resident map mapbyname
Directives opérationnelles callsite synchronize region allocate release advancedload delegatedstore

Concept de jeu de directives

modifier

L’un des points fondamentaux de l’approche proposée par le modèle OpenHMPP est le concept de directives, associées à des labels, qui rendent possible la mise en place d’une structure cohérente pour un jeu de directives entier, disséminé dans l’application.

Il existe deux types de labels :

  • Le premier est associé à un codelet. En général, les directives portant ce genre de label sont limitées à la gestion d’un seul codelet, appelé codelet autonome dans la suite de l’article (pour le distinguer d’un groupe de codelets).
  • Le second est associé à un groupe de codelets. Ces labels sont notés comme suit : “<LabelOfGroup>“, où “LabelOfGroup” est un nom spécifié par l’utilisateur. En général, les directives avec un label de cet type se rapportent au groupe entier. Le concept de groupe est réservé à une classe de problèmes qui nécessitent une gestion spécifique des données à travers l'ensemble de l’application, dans le but d'améliorer les performances.

Syntaxe des directives OpenHMPP

modifier

Dans le but de simplifier les notations, les expressions régulières seront utilisées pour décrire la syntaxe des directives OpenHMPP.

Les conventions de couleurs suivantes seront également utilisées :

  • Les mots-clés réservés à HMPP sont en bleu;
  • Les éléments de la grammaire qui peuvent être déclinés en mots-clés HMPP sont en rouge;
  • les variables de l’utilisateur reste en noir.

Syntaxe générale

modifier

La syntaxe générale des directives OpenHMPP est :

  • Pour le langage C :
#pragma hmpp <grp_label> [codelet_label]? directive_type [,directive_parameters]* [&]
  • Pour le langage FORTRAN :
!$hmpp <grp_label> [codelet_label]? directive_type [,directive_parameters]* [&]

où :

  • <grp_label> est un identifiant unique représentant un groupe de codelets. Dans le cas où aucun groupe n’est défini dans l’application, ce label peut être absent. Pour être valide, le nom du label doit obligatoirement suivre la grammaire suivante : [a-z,A-Z,_][a-z,A-Z,0-9,_]*. Notez que les deux caractères < et > appartiennent à la syntaxe et sont obligatoires dans ce contexte de label ;
  • codelet_label est un identifiant unique nommant un codelet. Pour être valide, le nom du label doit obligatoirement suivre la grammaire suivante : [a-z,A-Z,_][a-z,A-Z,0-9,_]*
  • directive est le nom de la directive ;
  • directive_parameters désigne les paramètres associés à la directive. Ces paramètres peuvent être de différents types et spécifient soit des arguments passés à la directive soit un mode d’exécution (asynchrone ou synchrone par exemple) ;
  • [&] est un caractère utilisé pour indiquer que la directive continue sur la ligne suivante (identique pour le C et le FORTRAN).

Paramètres des directives

modifier

Les paramètres associés à une directive peuvent avoir plusieurs types.

À suivre, les paramètres définis avec OpenHMPP :

  • version = major.minor[.micro]: spécifie la version des directives HMPP prise en compte par le préprocesseur;
  • args[arg_items].size={dimsize[,dimsize]*}: spécifie la taille des paramètres non scalaires (un tableau);
  • args[arg_items].io=[in|out|inout]: indique le statut des arguments de la fonction spécifiée (input, output ou les deux). Par défaut, les arguments non qualifiés sont des input;
  • cond = "expr": spécifie une condition pour l’exécution, sous forme d’une expression booléenne C ou FORTRAN qui doit être vraie pour que l’exécution du groupe ou du codelet commence;
  • target=target_name[:target_name]*: spécifie la liste des cibles à essayer d’utiliser, l’ordre d’apparition étant l’ordre de vérification de la disponibilité;
  • asynchronous: spécifie que l’exécution du codelet n’est pas bloquante (par défaut, synchrone);
  • args[<arg_items>].advancedload=true: indique que les paramètres spécifiés sont pré-chargés. Seuls les paramètres ayant pour statut in ou inout peuvent être pré-chargés;
  • args[arg_items].noupdate=true: spécifie que la donnée est déjà disponible sur le matériel et que son transfert n’est donc pas nécessaire. Lorsque cette propriété est fixée, aucun transfert n’est fait sur l’argument considéré;
  • args[<arg_items>].addr="<expr>": est une expression fournissant explicitement l’adresse de la donnée;
  • args[<arg_items>].const=true: indique que l’argument doit être téléchargé une et une seule fois.

Les directives OpenHMPP

modifier

Déclaration et exécution d'un codelet

modifier

Une directive codelet spécifie qu'une version de la fonction qui suit doit être optimisée pour un matériel spécifié.

Pour la directive codelet :

  • Le label du codelet est obligatoire et doit être unique dans l'application.
  • Le label du groupe n'est pas obligatoire si aucun groupe n'est défini.
  • La directive doit être insérée immédiatement avant la déclaration de la fonction concernée.

La syntaxe de la directive est:

#pragma hmpp <grp_label> codelet_label codelet 
                            [, version = major.minor[.micro]?]?
                            [, args[arg_items].io=[[in|out|inout]]*
                            [, args[arg_items].size={dimsize[,dimsize]*}]*
                            [, args[arg_items].const=true]*
                            [, cond = "expr"]
                            [, target=target_name[:target_name]*]

Il est possible d'avoir plusieurs directives codelet pour une fonction donnée, chacune spécifiant différents usages ou différents contextes d'exécution. Toutefois, il ne peut y avoir qu'une directive callsite pour un label codelet donné.

La directive callsite spécifie l'usage d'un codelet à un point donné dans le programme.

La syntaxe de la directive est:

#pragma hmpp <grp_label> codelet_label callsite
                     [, asynchronous]?
                     [, args[arg_items].size={dimsize[,dimsize]*}]*
                     [, args[arg_items].advancedload=[[true|false]]*
                     [, args[arg_items].addr="expr"]*
                     [, args[arg_items].noupdate=true]*

Un exemple à suivre :

/* déclaration du codelet */
#pragma hmpp simple1 codelet, args[outv].io=inout, target=CUDA
static void matvec(int sn, int sm, loat inv[sm], float inm[sn][sm], float *outv){
    int i, j;
    for (i = 0 ; i < sm ; i++) {
      float temp = outv[i];
      for (j = 0 ; j < sn ; j++) {
        temp += inv[j] * inm[i][ j];
    }
   outv[i] = temp;
 }
 
 int main(int argc, char **argv) {
   int n;
   ........
 
 /* Utilisation du codelet */
 #pragma hmpp simple1 callsite, args[outv].size={n}
 matvec(n, m, myinc, inm, myoutv);
   ........
 }

Dans certains cas, une gestion spécifique des données est nécessaire dans l'application (optimisation des mouvements de données entre CPU et GPU, variables partagées…).

La directive group permet la déclaration d'un groupe de codelets. Les paramètres définis pour cette directive sont appliqués à tous les codelets associés à ce groupe.

La syntaxe de la directive est:

#pragma hmpp <grp_label> group 
                          [, version = <major>.<minor>[.<micro>]?]? 
                          [, target = target_name[:target_name]*]? 
                          [, cond  = “expr]?

Directives appliquées aux transferts de données (optimisation des surcoûts liés aux communications)

modifier

Le principal goulet d'étranglement lors de l'utilisation du HWA réside souvent dans les transferts de données entre le matériel et le processeur principal.

Pour limiter les surcoûts liés aux communications, les transferts de données peuvent être communes à des exécutions successives d'un même codelet en utilisant la propriété asynchrone du matériel.

  • la directive « allocate »

Elle verrouille le matériel et alloue la quantité de mémoire nécessaire.

#pragma hmpp <grp_label> allocate [,args[arg_items].size={dimsize[,dimsize]*}]*
  • la directive « release »

Elle spécifie à quel moment le matériel doit être libéré pour un groupe ou pour un codelet autonome.

#pragma hmpp <grp_label> release
  • la directive « advancedload »

Elle charge les données avant l'exécution à distance du codelet.

#pragma hmpp <grp_label> [codelet_label]? advancedload
                  ,args[arg_items]
                  [,args[arg_items].size={dimsize[,dimsize]*}]*
                  [,args[arg_items].addr="expr"]*
                  [,args[arg_items].section={[subscript_triplet,]+}]*
                  [,asynchronous]
  • la directive « delegatedstore »

Elle constitue une barrière synchrone qui permet d'attendre la complétion de l'exécution d'un codelet asynchrone puis avant de télécharger les résultats.

#pragma hmpp <grp_label> [codelet_label]? delegatedstore 
                ,args[arg_items]
                [,args[arg_items].addr="expr"]*
                [,args[arg_items].section={[subscript_triplet,]+}]*
  • Calculs asynchrones

La directive synchronize spécifie qu'il faut attendre la fin de l'exécution asynchrone du callsite.
Pour cette directive, le label du codelet est toujours obligatoire et le label group est nécessaire si le codelet est associé à un groupe. La syntaxe de la directive est:

#pragma hmpp <grp_label> codelet_label synchronize
  • Exemple

Dans l'exemple qui suit, l'initialisation du matériel, l'allocation de la mémoire et le téléchargement des données d'entrée sont effectués une seule fois, en dehors de la boucle au lieu d'être effectués à chaque itération de la boucle.
La directive synchronize permet d'attendre la fin de l'exécution asynchrone du codelet avant de démarrer une autre itération. Finalement, la directive delegatedstore, placée en dehors de la boucle, télécharge le résultat de "sgemm".

int main(int argc, char **argv) {

#pragma hmpp sgemm allocate, args[vin1;vin2;vout].size={size,size}
#pragma hmpp sgemm advancedload, args[vin1;vin2;vout], args[m,n,k,alpha,beta]
  
for ( j = 0 ; j < 2 ; j ++) {
   #pragma hmpp sgemm callsite, asynchronous, args[vin1;vin2;vout].advancedload=true, args[m,n,k,alpha,beta].advancedload=true
   sgemm (size, size, size, alpha, vin1, vin2, beta, vout);
   #pragma hmpp sgemm  synchronize
}

#pragma hmpp sgemm delegatedstore, args[vout]
#pragma hmpp sgemm release

Partage de données entre codelets

modifier

Ces directives permettent de partager tous les arguments ayant le même nom pour tout un groupe.

Les types et dimensions de tous les arguments partagés doivent être identiques.

La directive map permet d'associer plusieurs arguments sur le matériel.

#pragma hmpp <grp_label>  map, args[arg_items]

La directive mapbyname est semblable à la directive map sauf que les arguments à mapper sont directement spécifiés par leur nom. La directive mapbyname est équivalente a de multiples directives map.

La notation est la suivante :

#pragma hmpp <grp_label> mapbyname [,variableName]+

Variable globale

modifier

La directive resident déclare des variables comme globales au sein d'un groupe.

Ces variables peuvent être directement accédées à partir de n'importe quel codelet du groupe sur le matériel (elles sont en quelque sorte considérées comme "résidentes" sur le matériel).

Cette directive s'applique à la déclaration qui la suit dans le code source.

La syntaxe de cette directive est :

#pragma hmpp <grp_label> resident 
               [, args[::var_name].io=[[in|out|inout]]*
               [, args[::var_name].size={dimsize[,dimsize]*}]*
               [, args[::var_name].addr="expr"]*
               [, args[::var_name].const=true]*

La notation ::var_name, avec le préfixe ::, indique une variable d'application déclarée comme resident.

Accélération de région

modifier

Une région est un mélange des directives codelet/callsite. Son but est d'éviter la restructuration du code imposée par la création explicite des codelets. Par conséquent, tous les attributs disponibles pour les directives codelet ou callsite peuvent être utilisés pour la directive region.

La syntaxe est la suivante :

#pragma hmpp [<MyGroup>] [label] region         
                           [, args[arg_items].io=[[in|out|inout]]*
                           [, cond = "expr"]<
                           [, args[arg_items].const=true]*
                           [, target=target_name[:target_name]*]
                           [, args[arg_items].size={dimsize[,dimsize]*}]*
                           [, args[arg_items].advancedload=[[true|false]]*
                           [, args[arg_items].addr="expr"]*
                           [, args[arg_items].noupdate=true]*
                           [, asynchronous]?
                           [, private=[arg_items]]*
   {
C BLOCK STATEMENTS
   }

Implémentations

modifier

OpenHMPP est basé sur la version 2.3 de HMPP (, CAPS entreprise).

Le modèle proposé par OpenHMPP est implémenté par :

  • CAPS Compilers, les compilateurs fournis par CAPS Entreprise pour le calcul hybride
  • PathScale ENZO Compiler Suite (support des GPU NVIDIA)

De plus, OpenHMPP est utilisé dans le cadre de projet HPC mené dans des domaines tels que le pétrole, l'énergie, l'industrie, l'éducation et la recherche et permet de développer des versions hautes performances de leurs applications, tout en préservant le code déjà produit.

Voir aussi

modifier

Références

modifier