Dans quels paradigmes de programmation devrais-je investir si je souhaite que mon code soit exécuté sur des machines petascale à l'avenir?


36

Un sondage parmi les 500 plus grands spéculateurs montre clairement que l’industrie tend à augmenter de manière exponentielle le nombre de cœurs transformés . Les plus grands superordinateurs utilisent tous MPI pour la communication entre les nœuds, bien qu'il ne semble pas y avoir de tendance claire pour le parallélisme sur nœud, l'approche la plus simple (mais pas nécessairement la plus efficace) pour mapper un processus MPI unique à chaque cœur, automatique. la parallélisation à partir du compilateur, OpenMP, pthreads, CUDA, Cilk et OpenCL.

Je fais partie d'un groupe de scientifiques qui entretiennent et développent un code susceptible d'être utilisé sur certains des plus gros superordinateurs au monde. En supposant un temps de développement limité, comment puis-je me préparer moi-même pour pouvoir tirer parti des performances de la machine la plus puissante du monde? Quelles hypothèses dois-je faire sur l'architecture d'interconnexion de processus? Quels paradigmes vont souffrir alors que nous entrons dans l'ère des âges? Les langues d'espace d'adressage global partitionnées seront-elles disponibles "en production" sur des machines petascale?


5
Je ne vois pas cette question correctement portée. De la FAQ, "Vos questions devraient être raisonnablement étendues. Si vous pouvez imaginer un livre entier qui répond à votre question, vous en demandez trop." En fait, chaque conférence SuperComputing à laquelle j'ai assisté a plusieurs panels sur ce sujet et il existe des dizaines à des centaines de livres consacrés à différents paradigmes de programmation
aterrel


5
Boule de cristal indisponible, feuilles de thé écrasées.
dmckee

Réponses:


34

Perspective historique

Il est vraiment impossible de dire à quoi ressembleront les nouveaux paradigmes à l'avenir, par exemple une bonne perspective historique que je suggère de lire dans Rise and Fall of HPF de Ken Kennedy . Kennedy donne un aperçu de deux modèles émergents, MPI par rapport à un compilateur intelligent, et explique en détail comment MPI avait le nombre approprié d'adopteurs précoces et la flexibilité nécessaire pour dominer. HPF a finalement résolu ses problèmes mais il était trop tard.

À bien des égards, plusieurs paradigmes, tels que PGAS et OpenMP, suivent cette même tendance HPF. Les premiers codes n'étaient pas assez flexibles pour être utilisés correctement et laissaient beaucoup de performances sur la table. Mais la promesse de ne pas avoir à écrire chaque iota de l’algorithme parallèle est un objectif attrayant. La recherche de nouveaux modèles se poursuit donc toujours.


Tendances claires en matière de matériel

Maintenant, le succès de MPI a souvent été cité comme étant étroitement lié à la manière dont il modélise le matériel sur lequel il fonctionne. En gros, chaque nœud a un nombre limité de processus et la transmission des messages à des points de contact locaux ou par le biais d'opérations collectives coordonnées s'effectue facilement dans l'espace du cluster. Pour cette raison, je ne fais confiance à personne qui donne un paradigme qui ne suit pas de près les nouvelles tendances du matériel, j'ai été convaincu de cette opinion par le travail de Vivak Sarakar .

Dans cette optique, trois tendances sont clairement en train de progresser dans les nouvelles architectures. Et laissez-moi être clair, il y a maintenant douze architectures différentes commercialisées dans HPC. Cela remonte à moins de 5 ans et ne comportait que x86, alors les prochains jours verront beaucoup d'opportunités pour utiliser le matériel de manières différentes et intéressantes.

  • Puces à usage spécial: pensez aux grandes unités vectorielles comme les accélérateurs (vue adoptée par Bill Dally de Nvidia)
  • Puces à faible consommation d'énergie: grappes basées sur ARM (pour gérer les budgets de puissance)
  • Carrelage de puces: pensez au carrelage de puces avec des spécifications différentes (travail d' Avant Argwal )

Modèles actuels

Le modèle actuel a 3 niveaux de profondeur. Bien que de nombreux codes utilisent bien deux de ces niveaux, peu d’entre eux ont été utilisés. Je crois que pour arriver d'abord à exascale, il faut investir pour déterminer si votre code peut fonctionner aux trois niveaux. C’est probablement la voie la plus sûre pour bien réitérer les tendances actuelles.

Permettez-moi de parcourir les modèles et la façon dont ils devront changer en fonction des nouvelles vues matérielles prévues.

Distribué

Les acteurs du niveau distribué tombent en grande partie dans les langues MPI et PGAS. MPI est clairement gagnant à l'heure actuelle, mais les langages PGAS tels que UPC et Chapel progressent dans l'espace. Le HPC Benchmark Challenge est une bonne indication. Les langages PGAS donnent des implémentations très élégantes des repères.

Le point le plus intéressant ici est que, même si ce modèle ne fonctionne actuellement qu’au niveau des nœuds, ce sera un modèle important au sein d’un nœud pour les architectures en mosaïque. Une indication est la puce Intel SCC, qui a fondamentalement agi comme un système distribué. L'équipe SCC a créé sa propre implémentation MPI et de nombreuses équipes ont réussi à transférer des bibliothèques de communauté vers cette architecture.

Mais pour être honnête, PGAS a vraiment une bonne histoire à jouer dans cet espace. Souhaitez-vous vraiment programmer l'internode MPI et que vous ayez à faire la même astuce intranode? Un gros problème avec ces architectures en mosaïque est qu’elles auront des vitesses d’horloge différentes sur les puces et des différences majeures de largeur de bande vers la mémoire; les codes performants doivent donc en tenir compte.

Mémoire partagée sur le nœud

Nous voyons ici que MPI est souvent "assez bon", mais les PThreads (et les bibliothèques dérivées de PThreads tels que Intel Parallel Building Blocks) et OpenMP sont encore souvent utilisés. L’opinion courante est qu’il y aura un moment où il y aura suffisamment de threads de mémoire partagée pour que le modèle de socket de MPI tombe en panne pour RPC ou vous avez besoin d’un processus plus léger fonctionnant sur le noyau. Vous pouvez déjà voir les indications selon lesquelles les systèmes IBM Bluegene rencontrent des problèmes avec la mémoire partagée MPI.

Comme Matt commente, la plus grande amélioration des performances des codes intensifs de calcul est la vectorisation du code série. Si de nombreuses personnes supposent que cela est vrai pour les accélérateurs, cela est également essentiel pour les machines sur nœud. Je crois que Westmere a une FPU large de 4, donc on ne peut avoir qu'un quart des flops sans vectorisation.

Bien que je ne vois pas OpenMP actuel entrer dans cet espace, il y a une place pour les puces de faible puissance ou les tuiles pour utiliser des threads plus légers. OpenMP a du mal à décrire le fonctionnement du flux de données et, à mesure que davantage de threads sont utilisés, je ne vois que cette tendance devenir de plus en plus exagérée. Il suffit de regarder quelques exemples de ce que l’on doit faire pour obtenir un prélecture adéquat avec OpenMP.

OpenMP et PThreads à un niveau de cours suffisant peuvent tirer parti de la vectorisation nécessaire pour obtenir un bon pourcentage de pic, mais cela nécessite de décomposer vos algorithmes de manière à ce que la vectorisation soit naturelle.

Co-processeur

Enfin, l’émergence du co-processeur (GPU, MIC, accélérateurs de cellules) s’est installée. Il devient de plus en plus évident qu’aucun chemin menant à exascale ne sera complet sans eux. À la SC11, tous les participants au prix Bell les utilisaient très efficacement pour atteindre les bas pétaflops. Tandis que CUDA et OpenCL dominent le marché actuel, j’espère que les compilateurs OpenACC et PGAS entreront dans l’espace.

Maintenant, pour arriver à exascale, une proposition consiste à coupler les puces de faible puissance à de nombreux co-processeurs. Cela va plutôt bien tuer la couche intermédiaire de la pile actuelle et utiliser des codes qui gèrent les problèmes de décision sur la puce principale et alourdir le travail des co-processeurs. Cela signifie que pour que le code fonctionne assez efficacement, une personne doit repenser les algorithmes en termes de noyaux (ou de codelets), c'est-à-dire des fragments parallèles de niveau d'instruction sans branche. Autant que je sache, une solution à cette évolution est plutôt ouverte.


Comment cela affecte le développeur de l'application

Passons maintenant à votre question. Si vous souhaitez vous protéger de la complexité imminente des machines Exascale, vous devez prendre certaines mesures:

  • Développez vos algorithmes pour s’adapter à au moins trois niveaux de hiérarchie parallèle.
  • Concevez vos algorithmes en termes de noyaux pouvant être déplacés entre les hiérarchies.
  • Détendez-vous pour tous les processus séquentiels, tous ces effets se produiront de manière asynchrone car l’exécution synchrone n’est tout simplement pas possible.

Si vous voulez être performant aujourd'hui, MPI + CUDA / OpenCL est suffisant, mais UPC y parvient. Ce n'est donc pas une mauvaise idée de prendre quelques jours et de l'apprendre. OpenMP vous permet de démarrer mais génère des problèmes lorsque le code doit être refactorisé. PThreads nécessite de réécrire complètement votre code dans son style. Ce qui fait de MPI + CUDA / OpenCL le meilleur modèle actuel.


Ce qui n'est pas discuté ici

Bien que toutes ces discussions sur exascale soient bonnes, quelque chose qui n’a pas vraiment été abordé ici est d’obtenir des données sur et hors des machines. Bien que de nombreux progrès aient été réalisés dans les systèmes de mémoire, nous ne les voyons pas dans les grappes de produits (trop chères). Maintenant que l'informatique à forte intensité de données occupe une place importante dans toutes les conférences de super-informatique, il y aura certainement un mouvement plus important dans l'espace de bande passante mémoire élevée.

Cela porte à l’autre tendance qui pourrait se produire (si les bons organismes de financement s’impliquent). Les machines vont devenir de plus en plus spécialisées pour le type d'informatique requis. Nous voyons déjà des machines «à forte intensité de données» financées par la NSF, mais ces machines sont sur une piste différente de celle du Grand Challenge Exascale 2019.

Cela est devenu plus long que prévu demander des références où vous en avez besoin dans les commentaires


2
Bien, mais comment pouvez-vous ignorer la vectorisation, qui est le facteur le plus important pour la performance sur les nœuds?
Matt Knepley

Très vrai (je considère en fait que cela fait partie du nœud de calcul spécial, je viens d'avoir une longue discussion avec Dr. Bandwidth sur la façon dont les fournisseurs suggèrent aux gens d'éteindre les unités vectorielles pour les codes série), j'ignore également les systèmes de mémoire, et / o. Je suppose que je vais ajouter ça maintenant.
aterrel

Les co-array de Fortran sont-ils à peu près équivalents à UPC?
Ondřej Čertík Le

Autant que je sache, il s’agit du même concept, mais je n’ai utilisé ni l’une ni l’autre des bibliothèques.
aterrel

En ce sens que CAF et UPC sont tous deux des PGAS, oui. Et ni est une bibliothèque, d'ailleurs. Il existe de nombreuses informations sur Internet pour répondre à cette question plus en détail.
Jeff

8

Commençons par une stratégie de code intranode (calcul qui ne touche pas l’interconnexion), car je pense que MPI est un bon choix pour le code internode. Je pense qu'il est absurde de parler de nœuds avec moins de 100 cœurs, donc au moins un GPU ou un MIC actuel.

C’est un fait que pthreads ne peut à lui seul vous offrir des performances maximales sur les puces modernes, car vous devez tirer parti de l’unité vectorielle (ce qui est vrai depuis le premier Cray). Sur Intel et AMD, vous pouvez utiliser des composants intrinsèques, mais ceux-ci ne sont pas portables et, à mon avis, maladroits. CUDA et OpenCL intègrent la vectorisation dans la bibliothèque et permettent d’obtenir des performances maximales. Tout le nouveau matériel dont je suis au courant a cette exigence de vecteur; toute solution doit donc en tenir compte. Pour moi, CUDA / OpenCL est la voie actuelle.

Ensuite, toutes ces machines seront NUMA, ce qui est plus difficile à programmer, mais je pense que la stratégie du noyau fonctionne. Vous divisez le travail et les données en petites unités. Celles-ci seront probablement planifiées automatiquement, comme cela se produit actuellement dans CUDA et OpenCL, mais vous pouvez spécifier des dépendances. Pour les problèmes qui correspondent au paradigme de la diffusion en continu, cette segmentation peut également être effectuée automatiquement. Intel TBB le fait, mais je préfère l’approche de bibliothèque de niveau supérieur illustrée par Thrust and Cusp , qui peut cibler CUDA ou (bientôt) TBB.


Je pense aussi que l'approche de CUDA / OpenCL a un avenir radieux ... mais laquelle prévaudra, CUDA ou OpenCL? Le récent fiasco d’AMD va-t-il nuire à OpenCL?
PhDP

2
Finalement, il y aura un standard ouvert que tout le monde utilise. Ce sera probablement OpenCL 2.0. Pour le moment, CUDA est un peu en avance, mais je peux facilement traduire 95% de mon code.
Matt Knepley

7

Je vais essayer une réponse plus courte que certains de mes estimés collègues sur ce fil ;-)

Mon message à tous mes étudiants est toujours que le temps de développement est plus précieux que le temps de calcul. Cela signifie que si vous avez le temps de convertir 100% du code avec une efficacité de 80% pour fonctionner sur de grandes machines - en utilisant une approche de haut niveau -, vous serez alors mieux loti que lorsque vous utilisez une bas niveau fastidieux. approche qui vous donne une efficacité de 100% sur 20% de votre code. En conséquence, je suis un grand fan des bibliothèques de haut niveau. Ce que je préfère dans ce domaine, ce sont les blocs de construction de threading (TBB), car ils me permettent d’examiner des algorithmes au niveau des boucles les plus externes et à un niveau élevé. Il peut également faire tout ce que vous voulez faire avec des têtes de lecture sans avoir à traiter des fonctions de système d'exploitation, etc. - donc pas d'OpenMP,

Je ne peux pas parler avec autorité de OpenCL, CUDA, etc.


4

Les réponses précédemment publiées sont excellentes, mais se sont principalement concentrées sur l'architecture de nœud, ce qui, à mon avis, reflète le fait que MPI est généralement considéré comme suffisant comme modèle de programmation internodale dans la plupart des cas. et qu’il s’agit d’un parallélisme intranode.

Voici mes tentatives pour répondre à deux questions qui ne sont pas encore résolues de manière relativement limitée:

Quelles hypothèses dois-je faire sur l'architecture d'interconnexion de processus?

Je considérerai trois propriétés de réseaux:

  1. latence,
  2. bande passante, et
  3. la simultanéité.

La latence est inversement proportionnelle à la fréquence. Nous savons que la mise à l'échelle de la fréquence a stagné. On peut donc en conclure qu'il est peu probable que la latence diminue de manière significative à l'avenir. La latence d’envoi / réception MPI sur Blue Gene / Q est d’environ 2 us, ce qui correspond à 3200 cycles. Les logiciels représentent plus de la moitié de cette latence, mais une bonne partie est requise par le standard MPI. Un réglage intensif pourrait réduire la latence à près de 1 us, en particulier si l’on peut affirmer que les caractères génériques MPI ne seront pas utilisés.

Dans tous les cas, la latence matérielle pour l'injection de paquets sur les systèmes Blue Gene et Cray est d'environ 1 us. L’augmentation de la simultanéité au niveau des nœuds rend difficile le maintien de ce nombre à un niveau aussi bas, mais je suis optimiste sur le fait que les concepteurs de matériel trouveront le moyen de maintenir la latence sous la barre des 5 dans un avenir proche.

La bande passante du réseau est augmentée de manière triviale en augmentant le nombre de liaisons réseau. Ce n'est cependant qu'une partie de l'histoire. On met 1000 liens sortants sur un nœud sans pouvoir les utiliser si le ou les processeurs ne peuvent pas gérer le réseau avec une bande passante complète. Par exemple, certains supercalculateurs goulot d’étranglement dans le bus (par exemple, HyperTransport) plutôt que sur le réseau, en termes de bande passante d’injection.

Il n'y a pas de limites fondamentales à la bande passante du réseau, seulement des limites pratiques. La bande passante coûte de l'argent et de l'énergie. Les concepteurs de systèmes devront tenir compte des compromis entre la bande passante du réseau et les autres parties de la machine lors du développement de futurs systèmes. De nombreux codes ne sont pas limités en bande passante réseau, il semble donc peu probable que nous voyions à l'avenir des machines avec une bande passante considérablement plus grande par connexion. Cependant, la bande passante par nœud devrait augmenter proportionnellement à la puissance de calcul, de sorte qu'il soit nécessaire de disposer de plusieurs connexions par nœud pour pouvoir être mis à l'échelle.

La troisième propriété des réseaux qui est souvent négligée dans les modèles formels est le nombre de messages pouvant être envoyés en une seule fois. Avoir un réseau avec une latence de 1 ns et / ou une bande passante de 1 To / s ne pouvant envoyer qu'un seul message à la fois serait totalement inutile pour la plupart des usages. Il est important de pouvoir envoyer de nombreux messages en même temps à partir de nombreux threads et pour que le réseau ne puisse pas s'effondrer en cas de conflit. Les systèmes Cray et Blue Gene atteignent maintenant plus de 1 MMPS (millions de messages par seconde). Je ne me souviens pas des chiffres exacts, mais les deux peuvent atteindre une fraction significative de la bande passante de pointe avec de petits messages. Un réseau idéal pourrait pouvoir atteindre le maximum de bande passante avec un message de n'importe quelle taille, mais cela est impossible dans la pratique en raison de l'en-tête de paquet et des coûts de comptabilité associés. cependant,

C'est une réponse incomplète et imparfaite. D'autres sont les bienvenus pour essayer de l'améliorer ou suggérer des choses que je devrais améliorer.

Les langues d'espace d'adressage global partitionnées seront-elles disponibles "en production" sur des machines petascale?

Les systèmes Cray XE, XK et XC ont un compilateur UPC et CAF de qualité production. Les systèmes Blue Gene peuvent être livrés avec XLUPC et XLCAF, mais personne ne le demande, il n'est donc pas livré. PERCS utilise des compilateurs XLUPC et XLCAF de qualité production, mais aucune installation à grande échelle accessible à la communauté scientifique.

Coarrays fait partie de Fortran 2008, bien que les implémentations dans Intel et GNU Fortran ne soient pas encore de grande qualité. L'implémentation Intel a la réputation de fonctionner mais également d'être assez lente (il y a un document à ce sujet chez PGAS12).

En ce qui concerne le modèle de programmation PGAS (puisque les modèles de programmation - et non les langages de programmation - sont le sujet de la question initiale), la bibliothèque Global Arrays constitue une approximation raisonnable de la qualité de la production dans de nombreux cas. En tant que moteur d'exécution, il n'est pas aussi robuste que MPI, mais MPI est assez unique en termes de qualité de mise en œuvre. La mise en œuvre ARMCI entre ARMCI et MPI rend les tableaux globaux beaucoup plus stables, bien que parfois plus lents.

Il est relativement facile d'implémenter des constructions de style PGAS de manière de qualité de production à l'aide de MPI-3 RMA. Si quelqu'un pose une nouvelle question à ce sujet, je serai ravi d'y répondre.


4
Vous pouvez poser la question sur la mise en œuvre de constructions de style PGAS dans MPI-3 vous-même (et y répondre vous-même), à ​​condition qu'il s'agisse d'un problème réel que vous avez rencontré dans le passé (ce qui, je suppose, est le même). Nous permettons aux utilisateurs de répondre à leurs propres messages.
Geoff Oxberry,

1
C’est l’une des questions les plus fréquemment posées, je suis heureux d’avoir la réponse de Jeff ici. EDIT: Je vois ce que vous entendez par là @ GeoffOxberry - oui, il devrait poser sa propre question et y répondre :)
Aron Ahmadia

D'accord, je vais essayer de réserver un peu de temps pour écrire une question-réponse «Quel est le lien entre PGAS et MPI-3 RMA» dans les deux prochaines semaines?
Jeff

3

De très grandes quantités de cœurs ouvrent également une perspective triviale mais étonnamment utile - il suffit de l’utiliser pour exécuter de nombreuses itérations de la simulation.

Une partie importante de la recherche informatique se résume aujourd'hui à balayer un espace de paramètres, à filtrer un grand pool de conditions initiales ou à calculer une distribution de certains résultats de manière à ré-échantillonner; toutes ces tâches sont parallèlement embarrassantes, donc à l'abri d'Amdahl


2

Je pense que même les réponses les plus réfléchies à cette question seront obsolètes dans cinq à dix ans. Compte tenu de l'incertitude des futurs paradigmes de programmation, il ne vaut peut-être pas la peine de passer beaucoup de temps à pré-optimiser votre base de code.


1
C'est trop fataliste - l'avenir est ici, aujourd'hui. La question concerne la pétascale, où nous en sommes aujourd'hui. Si vous ne pensez pas comment utiliser les 100 000 processeurs actuels, vous ne ferez pas beaucoup de progrès avec les 100 000 000 cœurs de demain.
Wolfgang Bangerth

1

J'étais sur le point de poster cette réponse à cette question , mais elle était fermée comme une copie de celle-ci, alors voici:

Cela peut sembler un peu Solomonic, mais selon mon expérience, l'avenir appartient aux approches hybrides dans lesquelles plusieurs noeuds multicœurs à mémoire partagée exécutant des noyaux multithreads sont connectés via un paradigme de mémoire distribuée tel que MPI.

Il y a cependant quelques problèmes et ils n'impliquent pas du tout le matériel. Tout d'abord, la plupart des programmeurs parallèles sont fortement investis dans des codes de type MPI et sont très réticents à être les premiers à réimplémenter des parties, ou la totalité, de leur base de code en utilisant un nouveau paradigme. Le manque de personnes utilisant des approches de mémoire partagée ralentit les progrès des algorithmes dans ce domaine, ce qui rend tout investissement encore plus inutile.

Un autre problème est que tout le monde associe le parallélisme de la mémoire partagée à OpenMP . Bien qu'OpenMP soit un moyen simple et rapide de résoudre des problèmes simples et de petite taille sur un petit nombre de processeurs, il s'agit d'un modèle de programmation absolument terrible pour un véritable parallélisme en mémoire partagée. Bien que nous ayons tous, à un moment ou à un autre, appris un certain nombre de paradigmes de programmation parallèle simples et efficaces , tels que les pools de threads ou les planificateurs , ils ne sont pas faciles à implémenter avec OpenMP et, franchement, ce n'est pas le type de parallélisme que OpenMP incite les programmeurs à utiliser.

En résumé, la barrière pour passer d'un paradigme de mémoire purement / partiellement partagée à une mémoire purement distribuée est assez élevée. Si vous souhaitez utiliser efficacement les threads, vous devez oublier OpenMP et gérer vous-même les threads et les accès concurrents (hello pthreads , au revoir Fortran).

Mais pourquoi adopter une approche hybride? Bien que MPI s’adapte à des milliers de cœurs, le modèle sous-jacent est celui de la synchronicité à cadenas et de modèles de communication statiques. Ceci est bon pour certains problèmes, par exemple les simulations de milliards de particules, mais sous-optimal pour des problèmes plus difficiles ou plus complexes. Les paradigmes de mémoire partagée facilitent l’équilibrage dynamique de la charge et / ou la communication asynchrone, mais cette opération nécessite un effort de programmation considérable.


1
Je conviens qu'OpenMP est un paradigme épouvantable et nuit à la communauté. Mais en même temps, il n’est pas vrai que l’alternative consiste à gérer vous-même les threads, les pools de threads, les files d’attente de travail, etc. Il existe en fait de très bonnes bibliothèques qui font exactement cela pour vous. Les Building Blocks d'Intel sont les plus remarquables. Cela fait des années que nous l'utilisons sous le capot de deal.II et cela fonctionne plutôt bien.
Wolfgang Bangerth

Hmm, je cherchais une application ou une bibliothèque robuste qui utilise TBB afin de vérifier que notre implémentation de BG fonctionne. J'ai seulement trouvé cise.ufl.edu/research/sparse/SPQR précédemment. Existe-t-il une chance que vous essayiez d'exécuter deal.II sur BGP ou BGQ en utilisant wiki.alcf.anl.gov/parts/index.php/BlueTBB si je fournissais l'allocation?
Jeff

@WolfgangBangerth: Déclenchez juste un avertissement pour vous car je crois que c'est à cela que le commentaire de Jeff était destiné. Bien que cela ne me dérangerait pas d'accéder à un BlueGene moi-même;)
Pedro

@ Jeff: Je serais prêt à essayer mais je ne pourrai probablement pas allouer trop de temps. N'hésitez pas à me contacter hors ligne. (@Pedro: Merci pour le heads-up!)
Wolfgang Bangerth
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.