• Aucun résultat trouvé

Modèle à mémoire distribuée

Dans le document eCandy Evolutionary Camera Network Deploy (Page 55-65)

Algorithmes évolutionnaires

3.2 Algorithmes évolutionnaires distribués

3.2.2 Modèle à mémoire distribuée

Ce modèle permet principalement l'exploitation d'un ensemble d'ordinateurs indé- pendants appelés nœuds. Chaque nœud lance un processus qui lui est propre, les pro- cessus communiquent entre eux en envoyant et en recevant des messages. Le programme correspondant au processus est le même pour chacun des nœuds. Le comportement et les données traitées par le processus sont déterminés en fonction de l'identité du pro- cessus. Les processus sont donc tous pareils, mais ils n'effectuent pas nécessairement les mêmes traitements.

L'implémentation du modèle à mémoire distribuée dans la librairie OpenBEAGLE fut d'abord l'objet d'un projet, BeagleHPC, réalisé à l'été 2007 dans le cadre du cours Design IV. L'objectif du projet était de distribuer les calculs de l'évaluation des in- dividus sur une grappe de calcul homogène à l'aide de la librairie OpenMPI. Quatre étudiants participèrent à la réalisation du projet : Mathieu Couillard, Maxime Girardin, Yannick Morin-Rivest et Félix-Antoine Fortin. L'équipe parvint à réaliser un prototype fonctionnel avec la version 3.0.3 d'OpenBEAGLE.

Le prototype comportait cependant certaines lacunes. Premièrement, il n'était pas fonctionnel avec les versions plus récentes d'OpenBEAGLE en raison de changements majeurs au niveau de l'interface de la librairie. Deuxièmement, les communications entre les nœuds de calcul n'étaient pas optimisées, de sorte que des individus qui ne devaient pas être évalués étaient néanmoins transmis aux nœuds évaluateurs. Troisièmement, les communications entre nœuds étaient toutes bloquantes, ce qui avait pour effet de ralen- tir les traitements. Quatrièmement, l'initialisation des générateurs de nombres pseudo- aléatoires était réalisée de manière indépendante sur chacun des nœuds de calcul, et la racine de chaque générateur n'était pas sauvegardée, les résultats étaient donc non reproductibles. Finalement, la gestion des erreurs n'était pas supportée, donc lorsqu'un processus se terminait inopinément, les autres processus demeuraient en suspens, et l'erreur n'était jamais acheminée jusqu'à l'usager.

Nous avions donc comme objectif pour l'implémentation du module HPC (High Per- formance Computing) d'adapter le projet BeagleHPC à la nouvelle interface d'Open- BEAGLE, et d'en corriger les lacunes. Nous présentons dans cette section le design final du module HPC, et son implementation.

Design

Le design de ce modèle consiste principalement à déterminer les traitements que devra réaliser chacun des nœuds. On doit ensuite déterminer comment doivent se trans- mettre les données entre les nœuds de calcul, la topologie des communications, et fina- lement déterminer quelles données doivent être envoyées afin que chaque nœud puisse réaliser ses tâches.

On identifie trois groupes de tâches distinctes dans un algorithme évolutionnaire. On associe à chacun de ces groupes de tâches un type de nœuds. Les nœuds du premier groupe sont nommés évoluateurs. Ils procèdent à l'évolution des individus d'un ou plusieurs dèmes : sélection, croisement, mutation et migration. Les nœuds du second groupe sont nommés évaluateurs. Ils évaluent l'adéquation des individus créés au cours de l'évolution. Finalement, le dernier groupe ne comprend qu'un seul nœud nommé superviseur. Il effectue la journalisation, le calcul des statistiques, et la sauvegarde des points de reprise.

Cette distribution des tâches implique donc un nombre minimum de trois nœuds. Chaque nœud évoluateur dispose d'un groupe de nœuds évaluateurs pour procéder à l'évaluation des individus. Les groupes d'évaluateurs n'étant pas partagés entre les évoluateurs, chaque groupe doit inclure au moins un évaluateur. De plus, chaque nœud évoluateur dispose d'au moins un dème. Ces contraintes sont résumées par les équations 3.1, 3.2, 3.3 où N représente le nombre total de nœuds, e le nombre de nœuds évolua- teurs, et d le nombre de dèmes.

N > 3 (3.1) N > 2e+ 1 (3.2)

d > e (3.3)

Les communications entre les nœuds sont structurées selon une topologie maître- esclave à trois niveaux. Le premier niveau est occupé par le superviseur. Il commu- nique principalement avec le niveau inférieur composé des évoluateurs. Le niveau des évoluateurs communique principalement avec le dernier niveau composé des évaluateurs. Cette topologie est illustrée à la figure 3.3.

Un algorithme évolutionnaire se déroule généralement en deux phases : l'initiali- sation, et l'évolution. L'initialisation est divisée en deux phases, l'initialisation des générateurs de nombres pseudo-aléatoires (GNPA) et l'initialisation des dèmes. Les

Superviseur Evoluateur Evaluateur Evoluateur Evaluateur Evaluateur

£

Evoluateur

i

Evaluateur Evaluateur

Evaluateur Evaluateur Evaluateur Evaluateur

i

Evaluateur

FIGURE 3.3 - Topologie des communications.

Superviseur

3

Initialisation du GNPA Distribution des racines

Q

du GNPA Initialisation

P

Evaluateur Evaluateur Initialisation du GNPA

FIGURE 3.4 - Communications durant l'initialisation des GNPA.

communications durant les deux phases d'initialisation sont représentées par les figures 3.4 et 3.5, et les communications durant la phase d'évolution sont représentées par la figure 3.6.

Durant la première phase d'initialisation, le nœud superviseur procède d'abord à l'envoi des racines des générateurs de nombres pseudo-aléatoires à chacun des nœuds évoluateur. Chaque racine est produite à partir du générateur de nombres pseudo- aléatoires du superviseur, on peut donc régénérer toutes les racines produites en connais- sant seulement la racine initiale du superviseur. Les résultats sont donc reproductibles à partir de cette seule racine. Une fois la racine reçue, chaque nœud évoluateur initialise son propre générateur de nombres pseudo-aléatoires.

I Superviseur I Evoluateur ] I Initialisation | ^ | d'un dème Envoi du dème Envoi des individus évalués

P

Calcul des statistiques Journalisation et sauvegarde Distribution des individus à évaluer Envoi des adéquations

P

Évaluation des individus Envoi des adéquations

P

Évaluation des individus

FIGURE 3.5 - Communications durant l'initialisation des dèmes.

Durant la seconde phase, l'initialisation est réalisée sur chacun des dèmes séquen- tiellement. Le superviseur initialise donc d'abord tous les individus du dème. Le dème est par la suite transmis au nœud évoluateur associé à celui-ci. Durant la phase d'initia- lisation, aucun opérateur n'est appliqué sur les individus, l'évoluateur distribue donc di- rectement les individus du dème reçu aux nœuds évaluateurs afin d'évaluer l'adéquation de chacun des individus. L'adéquation des individus est par la suite retournée à l'évolua- teur. Ce dernier renvoie finalement les individus évalués (soit tout le dème) au super- viseur qui à partir des individus calculent les statistiques, et sauvegarde un jalon. La même opération est effectuée pour chacun des dèmes de la population. Le superviseur calcule les statistiques pour la population lorsque tous les dèmes ont été évalués.

Durant la phase d'évolution, l'évoluateur procède d'abord à l'évolution du dème courant en appliquant un ensemble d'opérateurs : sélection, croisement, mutation. La modification d'un individu entraîne l'invalidation de son adéquation. Cependant, si un individu est simplement copié, son adéquation demeure valide. L'évoluateur distribue donc aux nœuds évaluateurs seulement les individus dont l'adéquation n'est plus va- lide, identifiés comme des individus "à évaluer" à la figure 3.6. Les évaluateurs évaluent ensuite les adéquations des individus, puis les renvoient à l'évoluateur. Ce dernier ren- voie finalement les individus évolués et évalués au superviseur. Ce dernier calcule les statistiques du dème, et de la population s'il s'agit du dernier dème, puis sauvegarde un jalon.

Evoluateur Envoi des individus évalués

P

Calcul des statistiques

P

Journalisation et sauvegarde "1 Évolution i ^ Idu dème * ■ « - Distribution des individus à évaluer Envoi des adéquations

P

Évaluation des individus Envoi des adéquations i I Évaluation | ^ J des individus

F I G U R E 3.6 ­ Communications durant l'évolution.

Les opérations réalisées sur un nœud et leurs résultats sont journalisées selon un niveau de détail variable. Le contenu de ce journal peut être affiché à l'écran ou encore écrit dans un fichier. L'écriture simultanée dans un fichier partagé par plusieurs nœuds pouvant entraîner des erreurs et des pertes de données, l'écriture est confiée au nœud su­ perviseur. Durant toutes les phases, les nœuds évoluateurs et évaluateurs peuvent faire parvenir leurs journaux au nœud superviseur de manière asynchrone. Le superviseur compile les journaux reçus, et produit un journal global pour tous les nœuds sur une base régulière. Les erreurs rencontrées par les différents nœuds sont directement ache­ minées au superviseur qui dispose des accès nécessaires pour communiquer à l'usager l'état des traitements, et les erreurs produites. La figure 3.7 illustre les communications asynchrones entre les nœuds et le superviseur pour l'envoi de journaux.

Implementation

L'implémentation dans OpenBEAGLE du design présenté à la section précédente a été réalisée à l'aide de la librairie MPI. MPI, pour Message Passing Interface, est une interface de programmation standard utilisée pour le calcul parallèle et distribué. L'interface définit un riche ensemble de méthodes de communications point à point, de synchronisation, de transferts de données, et de traitement global de données. Une

Superviseur I i Evoluateur 1 I ... I Evoluateur n I I Evaluateur 1 I... | Evaluateur n (+t- Envoi du journal Envoi du journal Envoi du journal Envoi du journal

FIGURE 3.7 - Communications des journaux.

application MPI peut être vue comme un ensemble de tâches s'exécutant en parallèle et communiquant entre elles les résultats de leur traitement. À chaque tâche est attribué un rang unique à l'intérieur de son contexte d'utilisation : un nombre entier entre 0 et N — 1 pour une application MPI de N tâches. Les rangs sont utilisés par les tâches pour s'identifier entre elles lors de l'envoi et la réception de données, l'exécution d'opérations collectives, et la coopération en général. Le concept de rang ajoute une couche d'abstraction lors de la programmation des communications. L'application MPI peut donc s'exécuter sur différentes interfaces de communication que ce soit TCP/IP, Ethernet, Myrinet, ou Infiniband sans modification au code source. Les tâches peuvent s'exécuter sur un seul processeur ou plusieurs processeurs en parallèle. Le nombre de tâches ne devrait cependant pas outrepasser le nombre de processeurs. Dans le cas contraire, il peut se produire un phénomène dit d'"oversubscription", qui entraîne une détérioration de la performance globale de l'application. La performance peut alors être inférieure à la performance de l'équivalent série de l'application.

Dans notre contexte d'utilisation, l'application MPI est composée de N processus identiques. La tâche réalisée par chaque processus est cependant différente, et elle est déterminée en fonction du rang du processus. L'équation 3.4 indique comment la tâche est déterminée en fonction du rang. Le superviseur occupe toujours le rang 0. Les évoluateurs occupent les rangs 1 à e, où e correspond au nombre d'évoluateurs définis. Finalement, les évaluateurs occupent les rangs supérieurs à e. Les valeurs de N et e

sont soumises aux contraintes 3.1 et 3.2.

{

Superviseur, si rang = 0; Evoluateur, si rang e [l,e] ; Evaluateur, si rang > e.

(3.4)

Chaque nœud evaluateur est associé à un évoluateur. Le rang du nœud évoluateur, aussi appelé nœud parent, auquel un evaluateur est associé est déterminé par la fonction 3.5. L'utilisation de cette fonction permet de minimiser à 1 la différence maximale du nombre d'évaluateurs entre deux évoluateurs. On limite ainsi la disparité entre les temps de calcul d'adéquation des évoluateurs.

EVOLUATEURPARENT(rang) = [(rang - 1 ) mod e] + 1 (3.5) Le tableau 3.1 résume la distribution des tâches en fonction du rang pour 12 noeuds et 3 évoluateurs. Il montre aussi la parenté entre les rangs des nœuds : par exemple, les nœuds de rang 4,7 et 10 sont les évaluateurs de l'évoluateur de rang 1.

Tâche Rang

Superviseur 0

Evoluateur 1 2 3

Evaluateur 4 7 10 5 8 11 6 9

TABLEAU 3.1 - Distribution des tâches en fonction du rang pour Af = 12, e = 3.

Plusieurs classes ont été développées à l'intérieur du module HPC afin de réaliser la distribution de l'évaluation à l'intérieur d'une application OpenBEAGLE. Nous présentons les principales dans les paragraphes suivants.

MPICommunication La classe MPICommunication constitue la pièce maîtresse du module HPC. Cette classe rassemble toutes les méthodes permettant l'initialisation et la réalisation des communications MPI à l'intérieur d'une application OpenBEAGLE. La classe implémente le patron de conception façade [17]. L'usager n'a donc pas à utiliser les fonctionnalités complexes de MPI afin de distribuer des éléments de son algorithme évolutionnaire, il n'a qu'à employer les fonctions simples qui lui sont fournies par la classe MPICommunication. Chaque nœud d'une application OpenBEAGLE HPC dispose d'une instance unique d'un objet MPICommunication. La classe est définie comme un singleton [18].

L'initialisation de l'objet sur un nœud entraîne 1*initialisation du module de commu- nication de MPI. L'initialisation implémente aussi les fonctions déterminant la tâche du nœud, et le rang de son parent immédiat, soit les équations 3.4 et 3.5. La détermination de la topologie par la classe MPICommunication permet un niveau d'abstraction supé- rieur à celui offert normalement par MPI. En effet, les communications MPI utilisent seulement un rang pour identifier un nœud lors d'une communication. Les communica- tions dans MPICommunication utilisent plutôt deux éléments : un groupe, et l'indice du nœud dans le groupe. Cette paire est par la suite convertie en un rang à l'intérieur de la classe.

En plus de l'abstraction au niveau de l'identifiant des nœuds, MPICommunication permet aussi de simplifier l'identification de messages inter-nœuds comparativement à MPI. Cette dernière nécessite qu'à chaque message soit associé un identifiant exprimé sous la forme d'un entier. La signification associée à un entier étant limitée, MPICom- munication associe plutôt une chaîne de caractères à un message au lieu d'un nombre. Cette chaîne de caractères est par la suite hachée pour produire un nombre unique. La fonction de hachage utilisée est SuperFastHash (SFH) de Paul Hsieh [24].

MPI permet l'envoi de données élémentaires : octet, entier, nombre à virgule flot- tante, et caractère. L'envoi de données plus complexe tel qu'une classe n'est pas di- rectement supporté par l'interface de programmation. Les transactions relevées dans le design précédent s'effectuant principalement sur des objets plutôt que sur des types atomiques, les objets sont sérialisés en une chaîne de caractères avant d'être transférés. Tout objet de la librairie OpenBEAGLE peut être sérialisé sous la forme d'une chaîne de caractères formatée en XML, et peut être initialise à partir d'une même chaîne.

Puisque tout objet d'une application OpenBEAGLE peut être sérialisé sous la forme d'une chaîne de caractères, la classe MPICommunication supporte seulement l'envoi et la réception de chaîne de caractères. Ce choix de design de la classe est motivé par la recherche d'une interface qui soit simple à utiliser, et par l'hypothèse que le temps de serialisation d'un objet est négligeable par rapport au temps d'évaluation des individus. De plus, les fonctions d'envoi permettent de compresser le message envoyé à l'aide de la librairie zlib. La compression peut être utile lorsque l'application s'exécute sur une réseautique à faible performance.

MPI permet d'envoyer une ou plusieurs données à la fois. Cependant, à la réception, la taille du message entrant doit être connue préalablement. Puisque les chaînes de ca- ractères représentant les objets peuvent être de différentes longueurs, tous les messages envoyés par la classe MPICommunication sont toujours précédés de l'envoi d'un entier indiquant la taille du message à venir. La fonction de réception de la classe MPICom-

munication attend d'abord la réception d'un message contenant la taille, puis ajuste la taille du second message attendue en fonction du contenu du premier message.

SwitchTypeOp L'algorithme d'une application OpenBEAGLE consiste à exécuter séquentiellement les opérateurs d'un ensemble sur une population d'individus. Afin de distribuer l'algorithme séquentiel, l'algorithme est divisé en trois tâches. La classe SwitchTypeOp implémente le patron de conception stratégie [19] afin de déterminer quel opérateur doit être appliqué en fonction du type de nœud sur lequel s'exécute l'application. Pour ce faire, la classe dispose d'un ensemble d'opérateurs par type de nœuds. À chaque itération, l'objet SwitchTypeOp interroge l'objet MPICommunication afin de déterminer le type de nœud, puis exécute séquentiellement chacun des opérateurs de l'ensemble associé au type. L'association entre les ensembles d'opérateurs et le type de nœud est effectuée grâce à une table de hachage. La complexité algorithmique de l'accès à un ensemble est donc 0(1). La construction du contenu des ensembles d'opérateurs est dynamique. Un usager peut donc facilement configurer son propre algorithme distribué sur différents types de nœuds. De plus, la nomenclature des types de nœuds est elle aussi générée dynamiquement. Un usager peut donc utiliser une distribution des opérateurs parmi un nombre variable de nœuds dont il choisit la nomenclature, dans la mesure où la topologie initialisée par l'objet MPICommunication correspond.

Randomizer La classe Randomizer du module HPC hérite de la classe Randomizer. Elle exécute les mêmes méthodes que la classe Randomizer de base dans OpenBEAGLE, sauf lors de l'initialisation. La figure 3.4 illustre les communications entre le nœud super- viseur et les nœuds évoluateurs. Ces interactions sont définies par la classe Randomizer du module HPC. Si le nœud est un superviseur, le générateur s'initialise normalement. Il génère ensuite un nombre de racines égales au nombre d'évoluateurs, puis les distri- bue à chaque évoluateur. Si le nœud est un évoluateur, il attend plutôt de recevoir la racine de son générateur, puis s'initialise. L'interface du Randomizer du module HPC étant la même que le Randomizer de base, son utilisation est transparente.

LoggerXMLD L'implémentation du modèle de transmission des journaux décrit à la section précédente est réalisée dans la classe LoggerXMLD du module HPC. Cette dernière hérite de la classe LoggerXML, et son utilisation est la même que la classe parente. L'usager peut donc utiliser l'un ou les autres des objets de journalisation de manière transparente.

console, ils disposent d'un tampon associé à chaque sortie dans laquelle ils peuvent écrire (fichier ou console). L'acheminement des journaux se fait en marge de l'exécution nor- male de l'algorithme évolutionnaire distribué. Chaque nœud dispose d'un fil d'exécution dédié à la transmission de son tampon de journalisation vers le nœud superviseur. Les opérations effectuées durant l'exécution de l'algorithme inscrivent des données de jour- nalisation dans le tampon. Le fil d'exécution procède à l'envoi du contenu du tampon au superviseur à intervalle régulier. Cependant, si le nombre de caractères contenus dans le tampon excède une limite donnée, le contenu est envoyé sans égard au temps. L'intervalle de temps et la limite du nombre de caractères sont tous deux paramétrables. Le nœud superviseur dispose aussi d'un fil d'exécution en marge de l'algorithme. Ce fil d'exécution est dédié à la réception du contenu des tampons envoyés par les nœuds. Il attend la réception d'un message en provenance de n'importe lequel des nœuds excluant le nœud superviseur. Lorsqu'un message est reçu, son contenu est ajouté au fichier de journalisation ou à la console.

Opérateurs de transmission Plusieurs opérateurs dérivés de la classe Operator ont été développés afin de réaliser la transmission des objets OpenBEAGLE durant l'algorithme évolutionnaire. Ces opérateurs accomplissent principalement deux tâches : soit l'envoi ou la réception d'objets. Ces deux actions sont représentées par les préfixes Send et Recv. Le préfixe Distribute est employé pour un seul opérateur et signifie que l'objet est décomposé en plusieurs sous-ensembles d'objets. Les objets peuvent être de différentes natures : adéquation (Fitness), dème (Deme), individu (Individual), individu modifié (Processed). Les objets peuvent être envoyés ou reçus des trois types de nœuds : superviseur, évoluateur, evaluateur. La nomenclature des opérateurs est donc définie en fonction des trois propriétés suivantes selon le patron suivant :

5end[Objet]To[Récepteur]Op (3.6) Recv[Objet]From[Somce]Op (3.7)

Le fonctionnement des opérateurs d'envoi est toujours le même : l'objet à envoyer est d'abord sérialisé en une chaîne de caractères XML, puis envoyer à l'aide de l'instance de MPICommunication. Cependant, le fonctionnement de l'opérateur responsable de la distribution des individus modifiés par les opérateurs d'un nœud évoluateur aux nœuds évaluateurs (DistributeDemeToEvaluatorsOp) est légèrement différent. Lorsqu'un in- dividu est modifié, son adéquation est invalidée. L'opérateur détermine donc d'abord quels individus doivent être évalués. L'ensemble de ces individus devant être évalués est

Envoi Réception DistributeDemeToEvaluators SendFitnessesToEvolver SendDemeToEvolver SendDemeToSupervisor SendProcessedToSupervisor RecvIndividualsFromEvolver RecvFitnessesFromEvaluators RecvDemeFromSupervisor RecvDemeFromEvolver RecvIndividualsFromEvolver

TABLEAU 3.2 - Opérateurs de transmission.

ensuite divisé en n parties, où n correspond au nombre d'évaluateurs à la disposition

Dans le document eCandy Evolutionary Camera Network Deploy (Page 55-65)

Documents relatifs