Arcane  v3.14.11.0
Documentation utilisateur
Chargement...
Recherche...
Aucune correspondance
Utilisation des accélérateurs (GPU)

Dans ce chapître, on appellera accélérateur un co-processeur dedié différent du processeur principal utilisé pour exécuter le code de calcul. Dans la version actuelle de Arcane, il s'agit d'accélérateurs de type GPGPU.

L'API Arcane pour gérer les accélérateurs s'inspire des bibliothèques telles que RAJA ou Kokkos mais se restreint aux besoins spécifiques de Arcane.

Note
L'API accélérateur de Arcane peut s'utiliser indépendamment des mécanismes associés aux codes de calcul tels que les modules, le maillage ou les services. Pour un exemple de fonctionnement autonome, se reporter au chapître Mode Autonome accélérateur.

L'implémentation actuelle supporte uniquement comme accélérateur les cartes graphiques NVidia (via CUDA) ou AMD (via ROCm).

L'API accélérateur de Arcane répond aux objectifs suivants:

  • unifier le comportement entre CPU séquentiel, CPU multi-thread et accélérateur.
  • avoir un seul exécutable et pouvoir choisir dynamiquement où sera exécuté le code : CPU ou accélérateur (ou les deux à la fois).
  • avoir un code source indépendant du compilateur et donc on n'utilise pas de mécanismes tels que les #pragma comme dans les normes OpenMP ou OpenACC.

Le principe de fonctionnement est l'exécution de noyaux de calcul déportés. Le code est exécuté par défaut sur le CPU (l'hôte) et certaines parties du calcul sont déportés sur les accélérateurs. Ce déport se fait via des appels spécifiques.

Pour utiliser les accélerateurs, il est nécessaire d'avoir compiler Arcane avec CUDA ou ROCm. Plus d'informations dans le chapitre Compilation.

Utilisation dans Arcane

L'ensemble des types utilisés pour la gestion des accélérateurs est dans l'espace de nom Arcane::Accelerator. Il y a deux composantes pour gérer les accélérateurs :

  • arcane_accelerator_core dont les fichiers d'en-tête sont inclus via #include <arcane/accelerator/core>. Cette composante comporte les classes indépendantes du type de l'accélérateur.
  • arcane_accelerator dont les fichiers d'en-tête sont inclus via #include <arcane/accelerator>. Cette composante comporte les classes permettant de déporter des noyaux de calcul sur un l'accélérateur spécifique.

Les classes principales pour gérer les accélérateurs sont:

  • IAcceleratorMng qui permet d'accéder à l'environnement d'exécution par défaut.
  • Runner qui représente un environnement d'exécution
  • RunQueue qui représente une file d'exécution
  • RunCommand qui représente une commande (un noyau de calcul) associée à une file d'exécution.

Il existe deux possibilités pour utiliser les accélérateurs dans Arcane :

Pour lancer un calcul sur accélérateur, il faut instancier une file d'exécution. La classe RunQueue gère une telle file. La fonction makeQueue() permet de créer une telle file. Les files d'exécution peuvent être temporaires ou persistantes mais ne peuvent pas être copiées. La méthode makeQueueRef() permet de créer une référence à une file qui peut être copiée.

Note
Par défaut la création de RunQueue à partir d'un Runner n'est pas thread-safe pour des raisons de performance. Si on veut pouvoir lancer plusieurs files d'exécution à partir de la même instance de Runner il faut appeler la méthode Runner::setConcurrentQueueCreation(true) avant

Utilisation dans les modules

Il est possible pour tout module de récupérer une implémentation de l'interface IAcceleratorMng via la méthode AbstractModule::acceleratorMng(). Le code suivant permet par exemple d'utiliser les accélérateurs depuis un point d'entrée :

// Fichier à include tout le temps
#include "arcane/accelerator/core/IAcceleratorMng.h"
#include "arcane/accelerator/core/RunQueue.h"
// Fichier à inclure pour avoir RUNCOMMAND_ENUMERATE
// Fichier à inclure pour avoir RUNCOMMAND_LOOP
using namespace Arcane;
using namespace Arcane::Accelerator;
class MyModule
{
public:
void myEntryPoint()
{
// Boucle sur les mailles déportée sur accélérateur
auto command1 = makeCommand(queue);
command1 << RUNCOMMAND_ENUMERATE(Cell,vi,allCells()){
};
// Boucle classique 1D déportée sur accélérateur
auto command2 = makeCommand(queue)
command2 << RUNCOMMAND_LOOP1(iter,5){
};
}
};
Types et macros pour gérer les énumérations des entités sur les accélérateurs.
#define RUNCOMMAND_ENUMERATE(ItemTypeName, iter_name, item_group,...)
Macro pour itérer sur accélérateur sur un groupe d'entités.
Types et macros pour gérer les boucles sur les accélérateurs.
#define RUNCOMMAND_LOOP1(iter_name, x1,...)
Boucle sur accélérateur avec arguments supplémentaires pour les réductions.
IAcceleratorMng * acceleratorMng() const override
Gestionnaire des accélérateurs.
virtual RunQueue * defaultQueue()=0
File d'exécution par défaut.
File d'exécution pour un accélérateur.
Module basique.
Definition BasicModule.h:45
Maille d'un maillage.
Definition Item.h:1178
CellGroup allCells() const
Retourne le groupe contenant toutes les mailles.
Espace de nom pour l'utilisation des accélérateurs.
RunCommand makeCommand(const RunQueue &run_queue)
Créé une commande associée à la file run_queue.
-*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-

Instance spécifique de Runner

Il est possible de créer plusieurs instances de l'objet Runner.

Une instance de cette classe est associée à une politique d'exécution dont les valeurs possibles sont données par l'énumération eExecutionPolicy. Par défaut, la politique d'exécution est eExecutionPolicy::Sequential, ce qui signifie que les noyaux de calcul seront exécutés en séquentiel.

Note
Lorsqu'on créé une instance de Runner sur accélérateur, il est possible de spécifier un autre accélérateur que l'accélérateur par défaut (si plusieurs sont disponibles). Cela complique significativement la gestion de la mémoire. Le chapître Gestion des multi-accélérateurs explique comment gérer cela.

Il est aussi possible d'initialiser automatiquement une instance de cette classe en fonction des arguments de la ligne de commande :

#include "arcane/accelerator/RunQueue.h"
using namespace Arcane;
using namespace Arcane::Accelerator;
Runner runner;
ITraceMng* tm = ...;
IApplication* app = ...;
initializeRunner(runner,tm,app->acceleratorRuntimeInitialisationInfo());
Gestionnaire d'exécution pour accélérateur.
Definition core/Runner.h:53
Interface de l'application.
Interface du gestionnaire de traces.

Compilation

Arcane propose une intégration pour compiler avec le support des accélérateurs via CMake. Ceux qui utilisent un autre système de compilation doivent gérer aux même ce support.

Pour pouvoir utiliser des noyaux de calcul sur accélérateur, il faut en général utiliser un compilateur spécifique. Par exemple, l'implémentation actuelle de Arcane via CUDA utilise le compilateur nvcc de NVIDIA pour cela. Ce compilateur se charge de compiler la partie associée à l'accélérateur. La partie associée au CPU est compilée avec le même compilateur que le reste du code.

Il est nécessaire de spécifier dans le CMakeLists.txt qu'on souhaite utiliser les accélérateurs ainsi que les fichiers qui seront compilés pour les accélérateurs. Seuls les fichiers utilisant des commandes (RUNCOMMAND_LOOP ou RUNCOMMAND_ENUMERATE) ont besoin d'être compilés pour les accélérateurs. Pour cela, Arcane définit les fonctions CMake suivantes :

  • arcane_accelerator_enable() qui doit être appelé vant les autres fonctions pour détecter l'environnement de compilation pour accélérateur
  • arcane_accelerator_add_source_files(file1.cc [file2.cc] ...) pour indiquer les fichiers sources qui doivent être compilés sur accélérateurs
  • arcane_accelerator_add_to_target(mytarget) pour indiquer que la cible mytarget a besoin de l'environnement accélérateur.

Si Arcane est compilé en environnement CUDA, la variable CMake ARCANE_HAS_CUDA est définie. Si Arcane est compilé en environnement HIP/ROCm, alors ARCANE_HAS_HIP est défini.

Exécution

Le choix de l'environnement d'exécution par défaut (IAcceleratorMng::defaultRunner()) est déterminé par la ligne de commande :

  • Si l'option AcceleratorRuntime est spécifiée, on utilise ce runtime. Actuellement les seules valeurs possibles sont cuda ou hip. Par exemple :
    MyExec -A,AcceleratorRuntime=cuda data.arc
  • Sinon, si le multi-threading est activé via l'option -T (voir Lancement d'un calcul), alors les noyaux de calcul sont répartis sur plusieurs threads,
  • Sinon, les noyaux de calcul sont exécutés en séquentiel.

Noyaux de calcul (RunCommand)

Une fois qu'on dispose d'une instance de RunQueue, il est possible de créér une commande qui pourra être déportée sur accélérateur. Les commandes sont toujours des boucles qui peuvent être de la forme suivante:

Le chapître Utilisation des lambda décrit la syntaxe de ces boucles.

Le code suivant permet par exemple d'utiliser les accélérateurs depuis un point d'entrée :

// Fichiers à inclure tout le temps
#include "arcane/accelerator/core/IAcceleratorMng.h"
#include "arcane/accelerator/core/RunQueue.h"
// Fichier à inclure pour avoir RUNCOMMAND_ENUMERATE
// Fichier à inclure pour avoir RUNCOMMAND_LOOP
using namespace Arcane;
using namespace Arcane::Accelerator;
class MyModule
{
public:
void myEntryPoint()
{
RunQueue* queue = ...;
// Boucle sur les mailles déportée sur accélérateur
auto command1 = makeCommand(queue);
command1 << RUNCOMMAND_ENUMERATE(Cell,vi,allCells()){
};
// Boucle classique 1D déportée sur accélérateur
auto command2 = makeCommand(queue)
command2 << RUNCOMMAND_LOOP1(iter,5){
};
}
};

Utilisation des vues

Les accélérateurs ont en général leur propre mémoire qui est différente de celle de l'hôte. Il est donc nécessaire de spécifier comment seront utilisées les données pour gérer les éventuels transferts entre les mémoires. Pour cela Arcane fournit un mécanisme appelé une vue qui permet de spécifier pour une variable ou un tableau s'il va être utilisé en entrée, en sortie ou les deux.

Avertissement
Une vue est un objet TEMPORAIRE et est toujours associée à une commande (RunCommand) et un conteneur (Variable Arcane ou tableau) et ne doit pas être utilisée lorsque la commande associée est terminée ou le conteneur associé est modifié.

Arcane propose des vues sur les variables (VariableRef) ou sur la classe NumArray (La page Utilisation de la classe NumArray décrit plus précisément l'utilisation de cette classe).

Quel que soit le conteneur associé, la déclaration des vues est la même et utilise les méthodes viewIn(), viewOut() ou viewInOut().

// Pour avoir les NumArray
#include "arcane/utils/NumArray.h"
// Pour avoir les vues sur les variables
// Pour avoir les vues sur les NumArray
// Tableaux 1D
// Variable 1D aux mailles
VariableCellReal var_c = ...;
// Vue en entrée (en lecture seule)
auto in_a = viewIn(command,a);
// Vue en entrée/sortie
auto inout_b = viewInOut(command,b);
// Vue en sortie (en écriture seule) sur la variable 'var_c'
auto out_c = viewOut(command,var_c);
Gestion d'une commande sur accélérateur.
Tableaux multi-dimensionnels pour les types numériques accessibles sur accélérateurs.

Gestion mémoire des données gérées par Arcane

Par défaut, Arcane utilise l'allocateur retourné par MeshUtils::getDefaultDataAllocator() pour le type NumArray ainsi que toutes les variables (VariableRef), les groupes d'entités (ItemGroup) et les connectivités.

Lorsqu'on utilise les accélérateurs, Arcane requiert que cet allocateur alloue de la mémoire qui soit accessible à la fois sur l'hôte et l'accélérateur. Cela signifie que les données correspondantes à ces objets sont accessibles à la fois sur l'hôte (CPU) et sur les accélérateurs. Pour cela, Arcane utilise par défaut la mémoire unifiée (eMemoryResource::UnifiedMemory).

Avec la mémoire unifiée, c'est l'accélérateur qui gère automatiquement les éventules transferts mémoire entre l'accélérateur et l'hôte. Ces transferts peuvent être coûteux en temps s'ils sont fréquents mais si une donnée n'est utilisée que sur CPU ou que sur accélérateur, il n'y aura pas de transferts mémoire et donc les performances ne seront pas impactées.

A partir de la version 3.14.12 de Arcane, il est possible de changer la ressoure mémoire utilisée par défaut via la variable d'environnement ARCANE_DEFAULT_DATA_MEMORY_RESOURCE. Sur les accélérateurs où la mémoire eMemoryResource::Device est accessible directement depuis l'hôte (par exemple MI250X, MI300A, GH200), cela permet d'éviter les transferts que peut provoquer la mémoire unifiée.

Dans tous les cas, il est possible de spécifier un allocateur spécifique pour UniqueArray et NumArray via les méthodes MemoryUtils::getAllocator() ou MemoryUtils::getAllocationOptions().

Arcane fournit des mécanismes permettant de donner des informations permettant d'optimiser la gestion de cette mémoire. Ces mécanismes sont dépendants du type de l'accélérateur et peuvent ne pas être disponible partout. Ils sont accessibles via la méthode Runner::setMemoryAdvice().

A partir de la version 3.10 de Arcane et avec les accélérateurs NVIDIA, Arcane propose des fonctionnalités pour détecter les transferts mémoire entre le CPU et l'accélérateur. La page Intégration avec CUPTI (Cuda Profiling Tools Interface) décrit ce fonctionnement.

Exemple d'utilisation d'une boucle complexe

L'exemple suivant montre comment modifier l'intervalle d'itération pour ne pas partir de zéro :

using namespace Arcane;
using namespace Arcane::Accelerator;
{
auto queue = makeQueue(runner);
auto command = makeCommand(queue);
auto out_t1 = viewOut(command,t1);
Int64 base = 300;
Int64 s1 = 400;
auto b = makeLoopRanges({base,s1},n2,n3,n4);
command << RUNCOMMAND_LOOP(iter,b)
{
auto [i, j, k, l] = iter();
out_t1(i,j,k,l) = _getValue(i,j,k,l);
};
}
#define RUNCOMMAND_LOOP(iter_name, bounds)
Boucle sur accélérateur.

Utilisation des lambda

Quelle que soit la macro (RUNCOMMAND_ENUMERATE(), RUNCOMMAND_LOOP(), ...) utilisée pour la boucle, le code qui suit doit être une une fonction lambda du C++11. C'est cette fonction lambda qui sera éventuellement déportée sur accélérateur.

Arcane utilise l'opérateur operator<< pour "envoyer" la boucle sur une commande (RunCommand) ce qui permet d'écrire le code de manière similaire à celui d'une boucle C++ classique (ou une boucle ENUMERATE_() dans le cas des entités du maillage) avec les quelques modifications suivantes :

  • les accolades ({ et }) sont obligatoires
  • il faut ajouter un ; après la dernière accolade.
  • le corps d'une lambda est une fonction et pas une boucle. Par conséquent, il n'est pas possible d'utiliser les mots clés tels que continue ou break. Le mot clé return est disponible et donc aura le même effet que continue dans une boucle.

Par exemple :

// Boucle 1D de 'nb_value' avec 'iter' l'itérateur
command << RUNCOMMAND_LOOP1(iter,nb_value)
{
// Code exécuté sur accélérateur
};
// Boucle sur les mailles du groupe 'my_group' avec 'cid' l'indice de
// la maille courante (de type Arcane::CellLocalId)
command << RUNCOMMAND_ENUMERATE(Cell,icell,my_group)
{
// Code exécuté sur accélérateur
};

Lorsque'un noyau de calcul est déporté sur accélérateur, il ne faut pas accéder à la mémoire associée aux vues depuis une autre partie du code pendant l'exécution sous peine de plantage. En général cela ne peut se produire que lorsque les RunQueue sont asynchrones. Par exemple :

#include "arcane/accelerator/Views.h"
using namespace Arcane::Accelerator;
queue.setAsync(true);
Arcane::Accelerator::RunCommand& command = makeCommand(queue);
auto in_a = viewIn(command,a);
auto out_b = viewOut(command,b);
// Copie A dans B
command << RUNCOMMAND_LOOP1(iter,nb_value)
{
auto [i] = iter();
out_b(i) = in_a(i);
};
// La commande est en cours d'exécution tant que la méthode barrier()
// n'a pas été appelée
// ICI il NE FAUT PAS utiliser 'a' ou 'b' ou 'in_a' ou 'out_b'
queue.barrier();
// ICI on peut utiliser 'a' ou 'b' (MAIS PAS 'in_a' ou 'out_b' car la
// commande est terminée)
void setAsync(bool v)
Positionne l'asynchronisme de l'instance.
Definition RunQueue.cc:292
void barrier() const
Bloque tant que toutes les commandes associées à la file ne sont pas terminées.
Definition RunQueue.cc:158

Limitation des lambda C++ sur accélérateurs

Les mécanismes de compilation et la gestion mémoire sur accélérateurs font qu'il y a des restrictions sur l'utilisation des lambda classiques du C++

Appel d'autres fonctions dans les lambda

Dans une lambda prévue pour être déportée sur accélérateur, on ne peut appeler que :

  • des méthodes de classe qui sont publiques
  • qui fonctions qui sont inline
  • qui fonctions ou méthodes qui ont l'attribut ARCCORE_HOST_DEVICE ou ARCCORE_DEVICE ou des méthodes constexpr

Il n'est pas possible d'appeler des fonctions externes qui sont définies dans d'autres unités de compilation (par exemple d'autres bibliothèques)

Utilisation des champs d'une instance de classe

Il ne faut pas utiliser dans les lambdas une référence à un champ d'une classe car ce dernier est capturé par référence. Cela provoquera un plantage par accès mémoire invalide sur accélérateur. Pour éviter ce problème, il suffit de déclarer localement à la fonction une copie de la valeur de l'instance de classe qu'on souhaite utiliser. Dans l'exemple suivant la fonction f1() provoquera un plantage alors que f2() fonctionnera bien.

class A
{
public:
void f1();
void f2();
int my_value;
};
void A::f1()
{
auto out_a = viewIn(command,a);
command << RUNCOMMAND_LOOP1(iter,100){
out_a(iter) = my_value+5; // BAD !!
};
}
void A::f2()
{
auto out_a = viewIn(command,a);
int v = my_value;
command << RUNCOMMAND_LOOP1(iter,100){
out_a(iter) = v+5; // GOOD !!
};
}

Utilisation du mécanisme d'échange de message

A partir de la version 3.10, Arcane supporte les bibliothèques MPI "Accelerator Aware". Dans ce cas, le buffer utilisé pour les synchronisations des variables est alloué directement sur l'accélérateur. Si une variable est utilisée sur accélérateur cela permet donc d'éviter des recopies inutiles entre l'hôte et l'accélérateur. Le mode échange de message en mémoire partagée supporte aussi ce mécanisme.

En cas de problèmes, il est possible de désactiver ce support en positionnant la variable d'environnement ARCANE_DISABLE_ACCELERATOR_AWARE_MESSAGE_PASSING à une valeur non nulle.

Gestion des multi-accélérateurs

Arcane associe lors de la création d'un sous-domaine une instance de Runner (accessible via ISubDomain::acceleratorMng()). Lorsqu'une machine dispose de plusieurs accélérateurs, Arcane choisi par défaut le premier qui est retourné dans les accélérateurs disponibles. Il est possible de changer ce comportement en positionnant la variable d'environnement ARCANE_ACCELERATOR_PARALLELMNG_RANK_FOR_DEVICE à une valeur strictement positive indiquant le modulo entre le rang de sous-domaine (retourné par IParallelMng::commRank() de ISubDomain::parallelMng()) et l'index de l'accélérateur dans la liste des accélérateurs. Par exemple si cette variable d'environnement vaut 8, alors le sous-domaine de rang N sera associé à l'accélérateur d'index (N % 8). Pour que ce mécanisme fonctionne, la valeur de cette variable d'environnemetn doit donc être inférieure au nombre d'accélérateurs disponibles sur la machine.

Gestion mémoire

Lorsque plusieurs accélérateurs sont disponibles sur une même machine, il existe en général un accélérateur "courant" pour chaque thread (par exemple avec CUDA il est possible de le récupérer par la méthode cudaGetDevice() et on peut le changer par la méthode cudaSetDevice()). Lorsqu'on alloue de la mémoire sur accélérateur, c'est sur cet accélérateur "courant" et cette mémoire ne sera pas disponible sur d'autres accélérateurs. Une instance de RunQueue est associée à un accélérateur donné et il faut donc s'assurer que les zones mémoires utilisées par une commande sont bien accessibles. Si ce n'est pas le cas cela produira une erreur lors de l'exécution (Par exemple, avec CUDA, il s'agit de l'erreur 400 dont le message est "invalid resource handle").

Si l'accélérateur "courant" a été modifié par exemple lors de l'appel à une bibliothèque externe il est possible de le changer en appelant la méthode Runner::setAsCurrentDevice().

Gestion des connectivités et des informations sur les entités

L'accès aux connectivités du maillage se fait différemment sur accélérateur que sur le CPU pour des raisons de performance. Il n'est notamment pas possible d'utiliser les entités classiques (Cell,Node, ...). A la place il faut utiliser les indentifiants locaux tels que CellLocalId ou NodeLocalId.

La classe UnstructuredMeshConnectivityView permet d'accéder aux informations de connectivité. Il est possible de définir une instance de cette classe et de la conserver au cours du calcul. Pour initialiser l'instance, il faut appeler la méthode UnstructuredMeshConnectivityView::setMesh().

Avertissement
Comme toutes les vues, l'instance est invalidé lorsque le maillage évolue. Il faut donc à nouveau appeler UnstructuredMeshConnectivityView::setMesh() après une modification du maillage.

Pour accéder aux informations génériques des entités, comme le type ou le propriétaire, il faut utiliser la vue ItemGenericInfoListView.

L'exemple suivant montre comment accéder aux noeuds des mailles et aux informations des mailles. Il parcourt l'ensemble des mailles et calcule le barycentre pour celles qui sont dans notre sous-domaine et qui sont des hexaèdres.

class TestConnectivity
{
public:
TestConnectivity(IMesh* mesh,RunQueue* queue)
: m_mesh(mesh)
, m_queue(queue)
, m_cells_center(VariableBuildInfo(mesh,"CellsCenterTest"))
{
m_connectivity_view.setMesh(mesh);
}
public:
void computeCenterForOwnHexa()
{
VariableNodeReal3& nodes_coord_var(m_mesh->nodesCoordinates());
auto command = makeCommand(m_queue);
auto in_node_coord = viewIn(command,nodes_coord_var);
auto out_cells_center = viewOut(command,m_cells_center);
// Cell->Node connectivity
Arcane::IndexedCellNodeConnectivityView cnc = m_connectivity_view.cellNode();
// Generic information for cells
Arcane::ItemGenericInfoListView cells_infos(m_mesh->cellFamily());
command << RUNCOMMAND_ENUMERATE(Cell,cid,m_mesh->allCells()){
if (!cells_infos.isOwn(cid))
return;
if (cells_infos.typeId(cid)!=IT_Hexaedron8)
return;
Real3 center;
// Iterate on nodes of Cell 'cid'
for( NodeLocalId node : cnc.nodes(cid) )
center += in_node_coord[node];
out_cells_center[cid] = center / 8.0;
};
}
private:
Arcane::IMesh* m_mesh = nullptr;
Arcane::VariableCellReal3 m_cells_center;
};
Vue sur les informations génériques d'une famille d'entités.
Vue sur les connectivités standards d'un maillage non structuré.

Opérations atomiques

La méthode doAtomic permet d'effectuer des opérations atomiques. Les types d'opérations supportées sont définies par l'énumération eAtomicOperation. Par exemple:

using namespace Arcane;
namespace ax = Arcane::Accelerator;
Arcane::Accelerator::RunQueue queue = makeQueue(m_runner);
auto command = makeCommand(queue);
auto inout_a = viewInOut(command, v_sum);
Real v_to_add = 2.1;
constexpr auto Add = ax::eAtomicOperation::Add;
command << RUNCOMMAND_LOOP1(iter, 100)
{
// atomic add 'v' to 'inout_a(iter)'
ax::doAtomic<Add>(inout_a(iter), v_to_add);
};

Algorithmes avancés: Réductions, Scan, Filtrage, Partitionnement et Tri

Arcane propose plusieurs classes permettant d'effectuer des algorithmes plus avancés. Sur accélérateur, ces algorithmes utilisent en général les bibliothèques proposées par le constructeur (CUB pour NVIDIA et rocprim pour AMD). Les algorithmes proposés par Arcane possèdent donc les mêmes limitations que l'implémentation constructeur sous-jacente.

Les classes disponibles sont:

Mode Autonome accélérateur

Il est possible d'utiliser le mode accélérateur de Arcane sans le support des objets de haut niveau tel que les maillages ou les sous-domaines.

Dans ce mode, il est possible d'utiliser l'API accélérateur de Arcane directement depuis la fonction main() par exemple. Pour utiliser ce mode, il suffit d'utiliser la méthode de classe ArcaneLauncher::createStandaloneAcceleratorMng() après avoir initialiser Arcane :

static StandaloneAcceleratorMng createStandaloneAcceleratorMng()
Créé une implémentation autonome pour gérer les accélérateurs.
static void init(const CommandLineArguments &args)
Positionne les informations à partir des arguments de la ligne de commande et initialise le lanceur.
Arguments de la ligne de commande.
Implémentation autonome de 'IAcceleratorMng.h'.

L'instance launcher doit rester valide tant qu'on souhaite utiliser l'API accélérateur. Il est donc préférable de la définir dans le main() du code. La classe StandaloneAcceleratorMng utilise une sématique par référence. Il est donc possible de conserver une référence vers l'instance n'importe où dans le code si nécessaire.

L'exemple 'standalone_accelerator' montre une telle utilisation. Par exemple, le code suivant permet de déporter sur accélérateur la somme de deux tableaux a et b dans un tabeau c.

#include <arcane/launcher/ArcaneLauncher.h>
#include "arcane/utils/NumArray.h"
#include "arcane/accelerator/core/IAcceleratorMng.h"
#include "arcane/accelerator/RunQueue.h"
void _testStandaloneLauncher()
{
using namespace Arcane;
// Créé une instance de Arcane::IAcceleratorMng autonome
// IMPORTANT: cette instance doit rester valide pendant
// toute l'exécution du progamme.
Arcane::StandaloneAcceleratorMng launcher(ArcaneLauncher::createStandaloneAcceleratorMng());
Arcane::IAcceleratorMng* acc_mng = launcher.acceleratorMng();
constexpr int nb_value = 10000;
// Teste la somme de deux tableaux 'a' et 'b' dans un tableau 'c'.
// Définit 2 tableaux 1D 'a' et 'b' et effectue leur initialisation sur CPU
for (int i = 0; i < nb_value; ++i) {
a(i) = i + 2;
b(i) = i + 3;
}
// Defínit le tableau 1D 'c' qui contiendra la somme de 'a' et 'b'
{
// Noyau de calcul déporté sur accélérateur.
auto command = makeCommand(acc_mng->defaultQueue());
// Indique que 'a' et 'b' seront en entrée et 'c' en sortie
auto in_a = viewIn(command, a);
auto in_b = viewIn(command, b);
auto out_c = viewOut(command, c);
// Réalise la somme sur accélérateur
command << RUNCOMMAND_LOOP1(iter, nb_value)
{
out_c(iter) = in_a(iter) + in_b(iter);
};
}
// Vérifie le résultat
Int64 total = 0.0;
for (int i = 0; i < nb_value; ++i)
total += c(i);
std::cout << "TOTAL=" << total << "\n";
Int64 expected_total = 100040000;
if (total != expected_total)
ARCANE_FATAL("Bad value for sum={0} (expected={1})", total, expected_total);
}
int main(int argc, char* argv[])
{
auto func = [&]
{
_testStandaloneLauncher();
};
}
#define ARCANE_FATAL(...)
Macro envoyant une exception FatalErrorException.
Interface du gestionnaire des accélérateurs.
Integer arcaneCallFunctionAndCatchException(std::function< void()> function)