Documente Academic
Documente Profesional
Documente Cultură
Thème
Optimisation automatique d’une classe de programmes
écrits en Halide.
Soutenu le :
02 JUILLET 2018
Promotion : 2017/2018
Remerciement
Je remercie tous les membres du jury d’avoir accepté d’examiner et de juger mon
modeste travail.
Je tiens à remercier Mme. AIT ALI YAHIA Dahbia, pour tous ses efforts fournis afin
d’assurer le bon déroulement des stages de fin d’étude.
Je dédie ce modeste travail aux membres de ma petite famille : mon cher papa, ma
chère maman, mes frères Amine et Chemss Eddine et ma petite sœur Meriem. Vous, qui
avez toujours cru en moi et à mes compétences, vous m’avez toujours soutenu et poussé
à aller toujours plus loin. Je vous remercie de m’avoir procuré l’environnement convivial
pour vivre, étudier, continuer et avancer dans ma vie.
Autant faire simple, je vous aime plus que tout dans ma vie ...
Je dédie aussi ce travail aux membres de ma grande famille, spécialement mon oncle
Omar pour son support et les suggestions qu’il me fournissait quant à mon parcours aca-
démique et professionnel.
A toute personne qui a lu mon rapport et qui m’a aidé à corriger ses éventuelles
erreurs : Zizou, mon oncle Omar, mon grand-père et Mme. Ghizlaine.
A tous mes amis pour tous les encouragements qu’ils m’ont donné durant l’année :
Zakaria, Fahima, Amine, Habiba, Mariem, Nesrine, Mouna, Kawthar, Amina et Besma
...
Aux membres de mon club GDG Algiers. Avec lesquels j’ai passé des moments inou-
bliables, et j’ai appris beaucoup de choses. Grâce au GDG, j’ai pu améliorer mes compé-
tences, booster mes connaissances, et même former une nouvelle famille. Je vous remercie
tous d’avoir fait partie de mon aventure : Tina, Asmaa, Manel, Amel, Azzeddine, Smail,
Milissa, Amine, Afaf et tous les autres.
Ikram
Résumé
L’accroissement des performances des architectures matérielles et leur hétérogénéité
ont rendu le développement d’application de plus en plus complexe. En effet, les appli-
cations doivent être transformées et optimisées pour exploiter au mieux les ressources
matérielles de la machine. Le processus d’optimisation d’un programme n’est pas simple,
qu’il soit mené par le programmeur ou par le compilateur ; car les transformations réali-
sées sur un programme peuvent être bénéfiques 1 dans certains cas, et mauvaises 2 dans
d’autres cas, tout dépend de plusieurs paramètres parmi lesquels les caractéristiques de
l’architecture d’exécution.
Notre projet de fin d’étude consiste à optimiser de façon automatique la classe des pro-
grammes implémentant les réseaux de neurones convolutifs (RNC) écrite dans le langage
Halide. Ce dernier est un nouveau langage et compilateur qui distingue entre l’algorithme
et les différentes optimisations qui lui sont appliquées. La classe des programme implé-
mentant les RNC est gourmande en termes de temps d’exécution surtout lorsqu’il s’agit
de l’exécuter sur une machine à base de CPU. L’objectif de notre travail est d’optimiser
un sous-ensemble des programmes de cette classe en un temps inférieur à 24 heures, et de
produire un code optimisé proche de celui développé à la main par les experts en matière
d’optimisation de programmes.
IV
Abstract
With the increase in hardware architectures performance, the development of powerful
applications is becoming increasingly difficult as they must be transformed and optimized
to take advantage of the machine material qualities.
The process of optimizing a program is not simple, whether it is done by the program-
mer or the compiler ; because the transformations carried out can be beneficial in some
cases, and bad in other ones, all depends on several parameters including the characteris-
tics of the execution’s architecture.
In this report, we are generally interested in compilers used techniques for automatic
optimization and in particular those techniques developed for Halide. Indeed, there are
already three automatic optimization techniques for Halide programs, two of which are
based on an empirical autotuning approach and the third is based on an analytic approach.
In this thesis, we present our automatic optimization method which is based on both
analytic and exploratory approach for CNN kernels optimization. We tested our method
on a set of seven benchmarks 3 , and we compared the execution time of the best program
built by our method and that optimized by experts. The suggested method generates
programs that are not only competitive but whose execution time is better than that of
programs optimized by hand in 96% of the tested cases.
V
ملخص
التطور الذي شهدته تصاميم الحواسيب الجديدة واختالفها عن بعضها البعض أدى إلى خلق
صعوبات أمام تطوير تطبيقات ذات سرعة عالية .حيث أصبح المبرمجون ملزمين بتطوير
نفس البرنامج وتحسينه بطريقة مختلفة من أجل كل تصميم للحصول على برنامج يستغل
كافة خصائص ذلك التصميم.
عملية تحسين البرامج ليست باألمر السهل ألن التحسينات المطبقة ال تزيد بالضرورة من
أداء البرنامج بل يمكنها أن تقلل من ادائه .ان مردود كل تحسين يتأثر بالعديد من العوامل
منها مواصفات التصميم.
يقوم مشروعنا على التحسين لصنف من البرامج المكتوبة بلغة البرمجة هاليد دورها
األساسي هو برمجة طبقات الشبكات العصبونية االلتفافية .هاليد عبارة عن لغة برمجة
ومترجم جديد له خاصية الفصل بين البرنامج والتحسينات المطبقة عليه .إن برامج الشبكة
العصبونية االلتفافية تستغرق وقتا ً طويالً عند تنفيذها خاصةً عندما تنفذ على مستوى وحدات
المعالجة المركزية .الهدف من مشروعنا هو التحسين التلقائي لهذا النوع من البرامج في
غضون مدة زمنية ال تتجاوز 24ساعة وإنتاج برامج محسنة أفضل من قريناتها.
من خالل هذه المذكرة سنتطرق بصفة عامة إلى المناهج المعتمدة من طرف المترجمين
للتحسين التلقائي للبرامج وبصفة خاصة إلى المناهج المعتمدة في هاليد .في الواقع 3تقنيات
طورت من أجل التحسين التلقائي لبرامج هاليد من بينهن اثنتان تقومان على منهج
االستكشاف أما االخرى فتتركز على المنهج التحليلي.
في هذه المذكرة نقترح تقنية جديدة للتحسين التلقائي لبرامج هاليد التي تعتمد على المنهج
االستكشافي والتحليلي في آن واحد .تقنيتنا تختص في تحسين برامج الشبكات العصبونية
االلتفافية .اختبرنا صحة وفعالية تقنيتنا بتطبيقها على مجموعة من سبعة برامج مرجعية
مختلفة .وكانت النتائج جد مرضية حيث أن البرامج المحسنة تلقائيا ً بواسطة تقنيتنا لها أوقات
تنفيذ تقارب تلك المحسنة من قبل خبراء في عالم تحسين البرمجيات.
Remerciement II
Dédicace III
Résumé IV
Abstract V
Table des figures XI
Liste des tableaux XIV
Introduction générale 1
Etude théorique 3
I Optimisations de programmes 4
I.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
I.2 Optimisations de boucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
I.2.1 Parallélisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
I.2.2 Vectorisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
I.2.3 Découpage en bandes . . . . . . . . . . . . . . . . . . . . . . . . . 6
I.2.4 Déroulage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
I.2.5 Interversion de boucles . . . . . . . . . . . . . . . . . . . . . . . . . 7
I.2.6 Coalescence de boucles . . . . . . . . . . . . . . . . . . . . . . . . . 8
I.2.7 Tuilage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
I.3 Autres optimisations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
I.3.1 Calcul redondant . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
I.3.2 Réorganisation de calcul . . . . . . . . . . . . . . . . . . . . . . . . 9
I.4 Difficulté du choix des bonnes optimisations pour un programme . . . . . . 10
I.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
II Approches d’optimisation automatique pour les compilateurs. 12
II.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
II.2 Approche exploratrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
II.2.1 Stratégies d’exploration d’espace . . . . . . . . . . . . . . . . . . . 13
II.2.2 Modes d’utilisation de l’approche exploratrice . . . . . . . . . . . . 14
II.2.3 Challenges des techniques basées sur l’approche exploratrice . . . . 14
II.3 Approche analytique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
II.3.1 Objectifs d’un modèle analytique . . . . . . . . . . . . . . . . . . . 15
II.3.2 Paramètres en entrée du modèle . . . . . . . . . . . . . . . . . . . . 15
II.3.3 Challenges des techniques basées sur l’approche analytique . . . . . 15
II.4 Approche prédictive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
II.5 Approche basée sur l’apprentissage automatique . . . . . . . . . . . . . . . 16
VII
II.5.1 Caractérisation des programmes . . . . . . . . . . . . . . . . . . . . 18
II.5.2 Algorithme d’apprentissage . . . . . . . . . . . . . . . . . . . . . . 19
II.5.3 Challenges des techniques basées sur l’apprentissage automatique . 20
II.6 Comparaison entre les approches d’optimisation automatique . . . . . . . . 20
II.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
III Halide et ses techniques d’optimisation automatique. 22
III.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
III.2 Algorithme dans Halide . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
III.3 Schedule dans Halide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
III.3.1 Optimisations à un seul étage . . . . . . . . . . . . . . . . . . . . . 24
III.3.2 Optimisations à deux étages . . . . . . . . . . . . . . . . . . . . . . 26
III.4 Techniques exploratrices pour l’optimisation automatique des programmes
Halide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
III.4.1 Technique basée sur l’algorithme génétique . . . . . . . . . . . . . . 27
III.4.2 Technique basée sur OpenTuner . . . . . . . . . . . . . . . . . . . . 29
III.4.3 Comparaison entre les deux techniques exploratrices . . . . . . . . . 30
III.5 Technique analytique pour l’optimisation des programmes Halide . . . . . 31
III.6 Comparaison entre les techniques de l’approche exploratrice et la technique
de l’approche analytique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
III.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Contributions 36
IV Conception 37
IV.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
IV.2 Description du problème . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
IV.2.1 Codage du schedule . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
IV.2.2 Fonction objectif . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
IV.3 Conception globale du système . . . . . . . . . . . . . . . . . . . . . . . . . 39
IV.3.1 Architecture globale du système . . . . . . . . . . . . . . . . . . . . 39
IV.3.2 Caractérisation des composantes du problème . . . . . . . . . . . . 41
IV.3.3 Fichier d’annotation . . . . . . . . . . . . . . . . . . . . . . . . . . 42
IV.3.4 Base de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
IV.3.5 Construction des schedules . . . . . . . . . . . . . . . . . . . . . . . 44
IV.4 Conception détaillée de la stratégie de construction des schedules . . . . . 46
IV.4.1 Recherche exhaustive . . . . . . . . . . . . . . . . . . . . . . . . . . 46
IV.4.2 Méthode Reorder-explore . . . . . . . . . . . . . . . . . . . . . . . . 47
IV.4.3 Méthode Reorder-analytique . . . . . . . . . . . . . . . . . . . . . . 50
IV.4.4 HalideAutotuner pour l’optimisation automatique des programmes
Halide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
IV.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
V Réalisation 59
V.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
V.2 Architecture du système . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
V.2.1 Programmes de la classe des RNC . . . . . . . . . . . . . . . . . . . 59
V.2.2 Système d’optimisation automatique pour les programmes de la
classe des RNC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
V.3 Technologies et bibliothèques utilisées . . . . . . . . . . . . . . . . . . . . . 61
V.4 Fonctionnalités du système . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
V.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
VI Tests et évaluations 64
VI.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
VI.2 Vue globale sur la phase d’évaluation . . . . . . . . . . . . . . . . . . . . . 64
VI.2.1 Caractéristiques des benchmarks . . . . . . . . . . . . . . . . . . . 64
VI.2.2 Optimisation automatique des benchmarks . . . . . . . . . . . . . . 65
VI.2.3 Architecture matérielle de test . . . . . . . . . . . . . . . . . . . . . 66
VI.3 Benchmarks de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
VI.3.1 Benchmark de convolution . . . . . . . . . . . . . . . . . . . . . . . 67
VI.3.2 Benchmark ReLU . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
VI.3.3 Benchmark Convolution-ReLU . . . . . . . . . . . . . . . . . . . . . 74
VI.3.4 Benchmark MaxPool . . . . . . . . . . . . . . . . . . . . . . . . . . 76
VI.3.5 Benchmark de la multiplication de matrices . . . . . . . . . . . . . 77
VI.3.6 Benchmark de la multiplication matricielle par lots . . . . . . . . . 79
VI.3.7 Benchmark de la multiplication matricielle transposée par lots . . . 80
VI.4 Synthèse des tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
VI.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Conclusion et perspectives 83
Annexes 86
A Analyse de dépendance 87
A.1 Validité d’une optimisation . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
A.2 Identification des dépendances . . . . . . . . . . . . . . . . . . . . . . . . . 88
B Détails sur quelques optimisations 90
B.1 Augmentation du taux de parallélisme par le déroulage . . . . . . . . . . . 90
B.2 Tuilage pour la localité des données . . . . . . . . . . . . . . . . . . . . . . 91
B.3 Difficulté du choix des optimisations pour un programme . . . . . . . . . . 93
C Le framework OpenTuner 96
C.1 Architecture logicielle d’OpenTuner . . . . . . . . . . . . . . . . . . . . . . 96
C.2 Types de paramètres dans OpenTuner . . . . . . . . . . . . . . . . . . . . 96
D Optimisation automatique des programmes Halide 99
D.1 Calcul du taux de réutilisation des données pour une fonction. . . . . . . . 99
D.2 Schedules raisonnables pour la population initiale de l’auto-scheduler . . . 100
D.3 Coût arithmétique d’une fonction pour l’auto-scheduler de Halide . . . . . 101
E Choix de conception 102
E.1 Diagramme de classe pour la recherche exhaustive des schedules Halide . . 102
E.2 Choix de conception pour la méthode HalideAutotuner . . . . . . . . . . . 104
E.2.1 Diagramme de classe du système . . . . . . . . . . . . . . . . . . . 104
E.2.2 Diagramme d’activité résumant l’exploration des optimisations . . . 106
F Modèle analytique pour l’optimisation d’interversion de boucle 107
G Réseaux de neurones et réseaux de neurones convolutifs 110
G.1 Réseaux de neurones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
G.1.1 Structure d’un neurone . . . . . . . . . . . . . . . . . . . . . . . . . 111
G.2 Réseau de neurones convolutif . . . . . . . . . . . . . . . . . . . . . . . . . 112
Bibliographie 114
Table des figures
XI
24 Test de performance d’un schedule construit sur le programme Halide en
entrée. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
25 Exemple d’algorithme Halide . . . . . . . . . . . . . . . . . . . . . . . . . 42
26 Annotation de l’algorithme ci-dessus. . . . . . . . . . . . . . . . . . . . . 43
27 Schéma de la base de données pour la sauvegarde des programmes et les
schedules testés. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
28 Méthodes conçues pour l’optimisation automatique des programmes Halide 45
29 Schéma résumant la construction des schedules dans l’exploration exhaus-
tive. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
30 Courbe montrant la différence entre l’application d’une bonne et d’une
mauvaise interversion de boucle sur les schedules renvoyés une fois l’in-
terversion fixée. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
31 Exploration en deux étapes dans la méthode Reorder-explore . . . . . . . 49
32 Introduction d’un modèle analytique pour le choix de l’optimisation d’in-
terversion de boucle dans Reorder-Analytique . . . . . . . . . . . . . . . 50
33 Evolution de la performance d’un schedule en faisant varier uniquement
l’optimisation compute_at. . . . . . . . . . . . . . . . . . . . . . . . . . . 53
34 Variante du Hill Climbing pour trouver une optimisation de granularité
de calcul (compute_at) de bonne qualité . . . . . . . . . . . . . . . . . . 54
35 Variante du Hill Climbing pour la recherche de bons facteurs de découpage
en bandes et de tuilage . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
36 Stratégie d’exploration d’espace adoptée dans HalideAutotuner . . . . . . 57
37 Architecture globale du système. . . . . . . . . . . . . . . . . . . . . . . . 60
38 Ligne de commande pour lancer la méthode de construction des schedules
HalideAutotuner. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
39 Optimisation automatique des programmes de test. . . . . . . . . . . . . 66
40 Optimisation parallèle des instances de test sur le cluster . . . . . . . . . 67
41 Principe de fonctionnement du traitement de convolution. . . . . . . . . . 67
42 Fichier d’annotation pour le programme de convolution pour des entrées
de 32 filtres de taille 5*5*16 et 32 images de taille 68*68*16 chacune. . . 68
43 Tableau comparatif entre les schedules construits automatiquement et
manuellement en fonction de la taille des entrée pour le programme de
convolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
44 Fichier d’annotation pour le programme de relu avec des images de taille
64*64*32*32. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
45 Fichier d’annotation pour le programme de Convolution-ReLU avec des
images de taille 68*68*16*32 et des filtres de taille 5*5*16*32. . . . . . . 75
46 Principe du maxpooling sur une image. . . . . . . . . . . . . . . . . . . . 76
47 S2 dépend de S1 (S2 ne peut pas s’exécuter en parallèle avec S1) . . . . . 87
48 S2 ne dépend pas de S1. (S1 et S2 s’exécutent en parallèle). . . . . . . . . 87
49 Boucle qui manipule une donnée multidimensionnelle A. . . . . . . . . . . 88
50 Boucle imbriquée qui manipule une donnée multidimensionnelle A. . . . . 88
51 Déroulage d’une boucle avec facteur k = 3 . . . . . . . . . . . . . . . . . 90
52 Code assembleur équivalent à celui de la boucle non déroulée de la figure
51 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
53 Code assembleur équivalent au programme déroulé de la figure 51 . . . . 91
54 Tuilage avec facteurs : n*m (les étendues des deux boucles internes). A[i,j]
et B[j,i] sont des données multidimensionnelles. A[i,j] est accessible ligne
par ligne, mais B[j,i] est accessible colonne par colonne. Après le tuilage,
A et B sont accessibles en blocs de taille n*m. . . . . . . . . . . . . . . . 92
55 Nombre de défauts de cache pour la version tuilée de la boucle, et pour
la version sans tuilage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
56 Implémentation naïve du programme à 3 tableaux : input, Blurx et Blury 94
57 Implémentation de la fenêtre coulissante sur le programme à 3 tableaux :
input, Blurx, Blury . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
58 Implémentation de la fusion totale sur le programme à trois tableaux :
input, Blurx et Blury. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
59 Schéma démontrant le flux de calcul des données dans l’implémentation
de la fenêtre coulissante sur le programme à trois tableaux. . . . . . . . . 95
60 Architecture globale d’OpenTuner. α, β, γ présentent des coefficients af-
fectés de la méta-heuristique du bandit multi-armé aux différentes tech-
niques de recherche, où celle qui dispose du plus grand coefficient sera
exécutée plus que les autres. . . . . . . . . . . . . . . . . . . . . . . . . . 97
61 Paramètres dans OpenTuner . . . . . . . . . . . . . . . . . . . . . . . . . 97
62 Exemple de déclaration d’un paramètre Halide à autorégler . . . . . . . . 98
63 Le corps de la fonction Blurx en Halide, extrait du corps de la fonction
de troublement d’une image. . . . . . . . . . . . . . . . . . . . . . . . . . 99
64 Code équivalent du parcours ligne par ligne de la fonction Blurx. . . . . . 99
65 Code équivalent du parcours colonne par colonne de la fonction Blurx. . . 100
66 Rectangle minimal qui englobe les valeurs de la fonction productrice f
nécessaires au calcul d’une valeur de la fonction consommatrice g. . . . . 100
67 Diagramme de classe pour la recherche exhaustive. . . . . . . . . . . . . . 103
68 Diagramme de classe pour la description des entités du système. . . . . . 105
69 Diagramme d’activité illustrant le fonctionnement de la génération de
schedules guidée par les restrictions. . . . . . . . . . . . . . . . . . . . . . 106
70 Boucle imbriquée de profondeur 2 d’étendues égales à N*M, qui manipule
les deux tableaux A et B (accès colonne par colonne) [Allen and Kennedy,
2002]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
71 Défauts de cache engendrés lors des premiers accès au tableau A 3*4. . . 108
72 Boucle imbriquée de profondeur 2 d’étendues égales à N*M, qui manipule
les deux tableaux A et B (accès ligne par ligne). . . . . . . . . . . . . . . 108
73 Schéma d’un réseau de neurones. . . . . . . . . . . . . . . . . . . . . . . 110
74 Structure d’un neurone artificiel. . . . . . . . . . . . . . . . . . . . . . . . 111
75 Exemple d’un réseau de neurones convolutifs. . . . . . . . . . . . . . . . 113
Liste des tableaux
XIV
Table des sigles et abréviations
AA Apprentissage Automatique
API Application Programmable Interface
ATLAS Automatically Tuned Linear Algebra Software
CISC Complex Instruction Set Computing
CNN Convolutional Neural Networks
CPU Central Processing Unit
GPU Graphics Processing Unit
JSON JavaScript Object Notation
ML Machine Learning
OCP Open Closed Principle
PFE Projet de Fin d’Etude
RISC Reduced Instruction Set Computing
RNC Réseaux de Neurones Convolutifs
SVM Support Vector Machine
VLIW Very Long Instruction Word
XV
Introduction générale
Ce processus d’optimisation n’est pas simple car les optimisations appliquées sur le
programme peuvent être invalides. Dans le cas où elles sont valides, elles peuvent être
bénéfiques (réduisent le temps d’exécution du programme) ou mauvaises (augmentent le
temps d’exécution du programme). Cela dépend de plusieurs paramètres dont : les carac-
téristiques matérielles de la machine qui est cencée exécuter le programme, la structure
et la dépendance entre les instructions du programme et les optimisations qui y sont déjà
appliquées.
Afin d’alléger la tâche d’optimisation aux programmeurs, les compilateurs ont été
conçus pour optimiser les programmes de façon automatique. Néanmoins, l’optimisation
automatique des programmes écrits dans des langages tels que C, C++ ou Java s’avère
complexe. En effet, un langage comme le C qui intégre les doubles pointeurs pour adres-
ser les données, n’est pas simple à optimiser car la théorie qui permet de décider si une
optimisation est valide ou pas devient indécidable.
L’objectif de l’équipe de recherche sur les compilateurs, Commit du MIT, est de déve-
lopper de nouveaux langages qui permettent d’exprimer des programmes de façon simple
et de pouvoir les optimiser automatiquement. L’un des langages développés par cette
équipe est Halide. Ce nouveau langage et compilateur a la particularité de séparer entre
le code et les optimisations qui y sont appliquées pour simplifier le passage d’une combi-
naison d’optimisations à une autre et cela sans alterner le code et son fonctionnement.
Notre projet de fin d’étude vise à contribuer dans l’optimisation automatique des pro-
grammes écrits dans Halide. Concrètement, notre objectif est d’élaborer un système qui
reçoit en entrée un programme Halide est renvoie en sortie les meilleures optimisations
à appliquer pour ce programme. Mais, ce n’est pas n’importe quel programme et pour
n’importe quelle architecture d’exécution. Notre projet se restreint à la classe des pro-
grammes implémentant les réseaux de neurones convolutifs, destinés à s’exécuter sur une
architecture matérielle à base de CPU. Le système élaboré doit prendre au minimum 24
heures pour aboutir à une combinaison d’optimisations qui minimise le temps d’exécution
du programme et qui est compétitive à celle développée par des experts.
Pour apporter une solution à notre problème, nous avons soulevé plusieurs questions :
Quelles sont les optimisations de programme concernées par notre projet et quelles sont
1
leurs caractéristiques et impact sur le programme ? Comment un compilateur optimise-t-il
un programme ? Quelles sont les méthodes d’optimisation automatique déjà développées
pour l’optimisation des programmes Halide ? Comment développer une nouvelle méthode
qui est censée donner des programmes Halide optimisés proches de ceux optimisés à la
main par les experts ?
Ce document commence par une étude théorique répartie en trois chapitres : dans
le premier, nous abordons les optimisations de programmes, le second énumère les ap-
proches et techniques utilisées par le compilateur pour optimiser les programmes, et dans
le troisième, nous présentons le langage Halide et les différentes méthodes d’optimisation
automatique développées pour ce compilateur. En second lieu, nous entamons la par-
tie contribution qui est répartie en trois chapitres : dans le premier, nous expliquons la
conception de notre méthode d’optimisation automatique appelée HalideAutotuner ainsi
que le système global, dans le second, nous expliquons nos différents choix technologiques
pour l’élaboration du système et dans le troisième, nous clôturons ce mémoire par les
résultats de l’optimisation automatique faite par HalideAutotuner sur sept programmes
implémentant les RNC.
2
Première partie
Etude théorique
Chapitre I
Optimisations de programmes
I.1 Introduction
Un programme doit subir plusieurs optimisations, pour pouvoir utiliser de façon effi-
cace les ressources matérielles de la machine et s’exécuter dans les temps impartis.
Au cours de ce chapitre, nous nous limitons aux optimisations utilisées dans le com-
pilateur Halide (les optimisations de boucles). Nous expliquerons le principe de chaque
optimisation abordée, son objectif et les ressources matérielles affectées par cette opti-
misation. Au final, nous clorons le chapitre avec un aperçu sur les compromis entre les
différentes optimisations abordées.
4
Chapitre I. Optimisations de programmes
I.2.1 Parallélisation
La parallélisation consiste à lancer en parallèle l’exécution d’un ensemble d’instruc-
tions séquentielles textuellement et qui sont indépendantes. La parallélisation de boucles
consiste à attribuer une ou plusieurs itérations d’une boucle à un thread à part, et les
exécuter toutes en parallèle et d’une façon asynchrone (voir figure 1). Cette optimisation
se base essentiellement sur les propriétés de l’architecture matérielle d’exécution. En effet,
ces machines doivent être équipées de plusieurs unités de traitement pour pouvoir exécu-
ter les instructions en parallèle (au moins deux).
Notons que le nombre d’unités fonctionnelles d’exécution dont dispose la machine est
un paramètre important pour l’optimisation de parallélisation. Car plus le nombre d’unités
fonctionnelles est grand, plus le traitement peut être parallélisé.
I.2.2 Vectorisation
La vectorisation d’une boucle consiste à repérer un ensemble de k éléments d’une don-
née multi-dimensionnelle qui subissent le même traitement à travers les itérations, à les
regrouper, à les rediriger vers les registres vectoriels, où ils subissent le traitement com-
mun [Allen and Kennedy, 2002]. L’exemple de la figure 2 illustre une boucle qui peut être
vectorisée.
Comme pour la parallélisation, l’utilisation des registres vectoriels entraîne une perte
de temps pour leur gestion. Par conséquent, il est plus judicieux d’utiliser la vectorisation
5
Chapitre I. Optimisations de programmes
lorsque le calcul vectorisé est important. La taille du registre vectoriel est un paramètre
matériel déterminant pour l’optimisation de vectorisation, car plus le registre est large,
plus on peut vectoriser et gagner en temps d’exécution.
I.2.4 Déroulage
C’est une transformation qui permet de réduire le nombre d’itérations d’une boucle
en répliquant son corps k fois tel que k <= n, où n représente le nombre d’itérations de la
boucle [Bacon et al., 1994]. La figure 4, illustre le déroulage d’une boucle avec un facteur
de déroulage k = 3. En effet, on transforme la boucle de dimension y, ayant un pas = 1, et
un nombre d’itérations = n+1, en une boucle, avec un pas = 3, et un nombre d’itérations
réduit à n+1 /3.
6
Chapitre I. Optimisations de programmes
cache d’instructions et ainsi de produire des défauts de cache. Par conséquent, cela va
augmenter le temps d’exécution.
Le nombre de registres est important pour l’optimisation de déroulage, car les données
des instructions déroulées vont allouer des regitres différents pour pouvoir s’exécuter en
parallèle. Donc, il faut prendre en considération le nombre de registres disponibles dans
la machine, pour pouvoir décider du facteur de déroulage (voir annexe B.1).
Notons que l’opération d’interversion n’est pas toujours légale, elle est soumise à des
contraintes de validité, comme nous l’avons souligné dans l’annexe A qui expose la théorie
2. Celle dont les résultats de l’itération i ne dépend pas des résultats de l’itération précédentes.
3. Si la machine stocke les tableaux ligne par ligne
7
Chapitre I. Optimisations de programmes
de l’analyse de dépendances.
Figure 6: La coalescence fusionne les deux boucles imbriquées sur la gauche pour
former une seule boucle : celle qui est sur la droite
Si on veut paralléliser la boucle la plus externe et que son étendue n’est pas assez
large alors le surcoût de la gestion des threads va affecter le temps d’exécution lors de
l’application de la parallélisation. Alors on procède à une coalescence de boucles, pour
produire une nouvelle boucle avec une étendue plus large, et ensuite la paralléliser 4 .
Néanmoins, la coalescence de boucles, peut nuire au parallélisme et à la vectorisation, car
elle risque de fusionner deux boucles dont l’une est dépendante et l’autre indépendante
et produire une boucle dépendante qui ne peut être ni parallélisée ni vectorisée.
I.2.7 Tuilage
Il est connu sous le nom de tiling ou loop blocking. C’est une optimisation qui est
obtenue en combinant trois optimisations : deux découpages en bandes pour deux boucles
imbriquées (chacune avec un facteur de découpage n et m) suivis d’une réorganisation de
boucles, dans ce cas n*m sont dits les facteurs de tuilage. La transformation de tuilage
est réalisée sur les boucles qui manipulent des données multi-dimensionnelles indexées en
fonction des indices de boucles. Suite à l’application du tuilage, ces données vont être
accédées tuile par tuile de taille n*m au lieu qu’elles soient accédées ligne par ligne ou
colonne par colonne et cela afin d’améliorer leur localité de données (voir figure 7).
Le tuilage peut augmenter le taux de parallélisme car si les données des tuiles (les
données manipulées dans les deux boucles les plus internes) sont indépendantes les unes
des autres, on peut paralléliser l’exécution du programme sur les tuiles de données. Néan-
moins, cela peut détériorer les performances quand il n’y a pas de localité de données,
car au lieu de tirer bénéfice du chargement « ligne par ligne » établi par le prélecteur,
4. Si elle reste indépendante.
8
Chapitre I. Optimisations de programmes
Figure 7: Un tuilage avec facteurs : n*m (les étendues des deux boucles internes).
A[i,j] et B[j,i] sont des données multidimensionnelles. A[i,j] est accessible ligne par
ligne, mais B[j,i] est accessible colonne par colonne. Après le tuilage, A et B sont
accessibles en blocs de taille n*m.
on abandonne cet avantage pour passer à d’autres données qui ne sont pas nécessaires au
calcul.
Les facteurs de tuilage doivent être bien choisis : assez petits pour permettre une
grande granularité de parallélisme, et pour assurer qu’une tuile peut être chargée entiè-
rement dans le cache. De même, ils doivent être assez grands pour exploiter l’espace du
cache de façon efficace. La transformation de tuilage n’est pas toujours possible. En effet,
c’est une transformation qui combine le découpage en bandes et l’interversion de boucles.
Comme la transformation d’interversion de boucles n’est pas toujours légale, et qu’elle
est soumise à des conditions de validité (voir l’annexe A pour plus de détails sur l’analyse
de dépendance : la théorie qui permet de décider de la validité d’une optimisation sur le
code), le tuilage est ainsi soumis aux mêmes conditions.
9
Chapitre I. Optimisations de programmes
Par conséquent, le choix des optimisations à appliquer sur un programme est complexe
et nécessite une connaissance préalable des propriétés de l’architecture matérielle : la taille
du registre vectoriel et la taille du cache. Ainsi, une connaissance des avantages et des
inconvénients de chaque optimisation est primordiale pour atteindre un bon niveau de
10
Chapitre I. Optimisations de programmes
I.5 Conclusion
Nous avons abordé dans ce chapitre quelques optimisations de programmes nécessaires
pour améliorer les performances des applications et réduire leur temps d’exécution. Nous
nous sommes focalisés particulièrement sur les optimisations de boucles, auxquelles nous
avons rajouté l’optimisation du calcul redondant et l’optimisation de réorganisation de
code, car elles sont utilisées dans le compilateur Halide.
De cette étude, il ressort qu’une optimisation n’est pas nécessairement bénéfique pour
un programme ou pour un segment de programme, et donc son application doit être réflé-
chie. L’utilisation d’une optimisation est soumise à plusieurs contraintes qui prennent en
considération : les propriétés de l’architecture d’exécution (la taille du registre vectoriel, le
nombre de processeurs), l’impact de chaque optimisation de façon générale, et la relation
entre les différentes optimisations. En outre, il existe des optimisations qui ne sont pas
censées améliorer directement les performances, mais elles représentent un prétraitement
pour l’application d’autres optimisations.
Pour faciliter la tâche des programmeurs, les compilateurs ont été conçus pour opti-
miser de façon automatique les programmes développés. Dans le chapitre suivant, nous
examinerons des approches utilisées par les compilateurs pour automatiser le processus
d’optimisation de programmes.
11
Chapitre II
II.1 Introduction
Dans le chapitre précédent, nous avons abordé quelques optimisations récurrentes et
qui peuvent être appliquées à un programme, dans le but d’améliorer son temps d’exé-
cution. Nous avons constaté que la tâche d’optimisation d’un programme n’est pas facile
dû à l’interdépendance entre les instructions du programme, l’ordre et l’organisation des
instructions ainsi que les propriétés de l’architecture matérielle d’exécution.
Pour soulager les programmeurs de la tâche d’optimisation, il a été intégré aux compi-
lateurs des techniques qui cherchent la meilleure combinaison d’optimisations à appliquer
pour un programme. Mais cette tâche constitue un problème NP-difficile car l’espace de
recherche des optimisations pouvant être appliquées à un programme est immense, et le
test de toutes les combinaisons d’optimisations prend un temps exponentiel [Stephenson
et al., 2003].
L’espace des implémentations est régi par l’ensemble des optimisations que le compi-
lateur peut appliquer sur un programme, ainsi que les paramètres de chacune des optimi-
sations. Par exemple : la taille de tuilage, le facteur de déroulage, le facteur de vectori-
sation [Hall, 2011]. Ces paramètres auront plusieurs valeurs possibles, et chaque type de
12
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
paramètre se verra combiner avec d’autres pour former les différentes combinaisons d’op-
timisation. Ces combinaisons seront, ensuite, appliquées sur le programme à optimiser,
donnant naissance à des implémentations (des variantes pour le même programme) [Park
et al., 2011]. Pour simplifier, on peut considérer qu’une combinaison d’optimisations est
un vecteur à éléments binaires O=({0,1}n), où chaque oi , appartenant à O, fait référence
à une optimisation i qui peut être appliquée au programme. Si oi = 1, alors l’optimisa-
tion i est effectivement appliquée au programme. Sinon, elle ne l’est pas. Par conséquent,
l’espace de toutes les combinaisons d’optimisations est de 2n (voir figure 8).
Algorithme génétique : Il a été utilisé dans [Ragan-Kelley et al., 2013] pour l’optimi-
sation automatique des programmes écrits dans le langage Halide. En effet, dans [Ragan-
Kelley et al., 2013], on considère qu’une combinaison d’optimisations, applicable au pro-
gramme à optimiser, est un individu dont la fitness est mesurée par le temps d’exécution
du programme auquel on applique cette combinaison d’optimisations. L’algorithme d’op-
timisation automatique commence par générer une population aléatoire d’individus sur
laquelle il applique les différents opérateurs de l’algorithme génétique 1 pendant N itéra-
1. mutation, croisement, sélection ...etc.
13
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
Mode en ligne : On est sur ce mode lorsque l’exploration des combinaisons d’op-
timisations se fait à l’exécution du programme, c’est-à-dire, au moment où toutes les
informations sur le programme sont disponibles.
Mode hors ligne : On dit que l’Autotuning est appliqué en mode hors ligne, lorsqu’il
se fait en temps de compilation où on ne dispose pas de toutes les données utilisées au
cours de l’exécution.
14
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
Plusieurs modèles analytiques ont été proposés dans [Allen and Kennedy, 2002] afin
de choisir une bonne valeur pour une seule optimisation uniquement : un modèle pour
le choix d’une bonne taille de tuilage, un autre pour le choix d’une bonne optimisation
d’interversion de boucles ...etc. Selon [Allen and Kennedy, 2002] l’optimisation d’inter-
version de boucle est une optimisation qui a pour objectif de maximiser l’utilisation de
la mémoire cache et des registres. Donc, le modèle s’est basé sur l’estimation du taux de
défaut de cache et ne reçoit en entrée que la taille d’une ligne de cache (voir l’annexe F
pour une explication détaillée du modèle d’interversion de boucle).
15
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
l’effet de chacune d’elles, et son impact sur les autres. Par conséquent, un modèle ana-
lytique robuste doit être complexe, car il prend nécessairement un grand ensemble de
paramètres en considération.
Une technique basée sur l’approche prédictive a été conçue pour l’optimisation auto-
matique de la multiplication de matrices, afin de réduire le temps pris par ATLAS 3 durant
les décisions d’optimisation. Cette technique combine entre l’approche exploratrice d’AT-
LAS et l’approche analytique développée dans [Yotov et al., 2003] pour l’optimisation
automatique de la multiplication de matrices. L’approche exploratrice implémentée dans
ATLAS cherche de façon presque exhaustive des paramètres pré-élaborés pour l’optimi-
sation : taille du tuilage de premier niveau, taille du tuilage de second niveau et le facteur
de déroulage. Ce qui prend énormément de temps pour choisir les bons paramètres d’op-
timisation, c’est le fait de tester toutes les combinaisons de ces paramètres, les compiler
et les exécuter. L’approche prédictive a été conçue pour utiliser les facteurs estimés par le
modèle analytique de [Yotov et al., 2003] et les tester sur le programme de la multiplica-
tion de matrices. Ensuite, cette technique prédictive procède à un ensemble de tests (sur
un espace beaucoup plus restreint que celui d’ATLAS) en choisissant d’autres facteurs, et
tentant d’atteindre le meilleur facteur de tuilage. La figure 9 montre la courbe de variation
de la performance du programme de multiplication de matrice en fonction des tailles de
tuilage adjacentes à celle retournée par le modèle de [Yotov et al., 2003].
Les challenges de cette technique combinent entre ceux de la technique exploratrice
et ceux de la technique analytique. La difficulté de cette technique réside dans la qualité
du modèle car si le modèle n’est pas bien étudié, il peut centrer l’espace de recherche
autour des combinaisons d’optimisations de mauvaise qualité, ou donner un sous-espace
à explorer qui reste toujours assez grand.
3. La bibliothèque d’optimisation automatique qui se base sur la recherche exhaustive afin d’optimiser
le programme de multiplication de matrices.
16
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
programmes. Une fois le modèle construit, il doit pouvoir généraliser et décider des op-
timisations à appliquer pour un nouveau programme en entrée (qui n’appartient pas à
l’ensemble d’apprentissage). Cela consiste à analyser les caractéristiques du programme à
optimiser et à les corréler avec celles des programmes de l’ensemble d’apprentissage, pour
enfin lui appliquer les optimisations correspondantes (voir figure 10).
Figure 10: Utilisation du modèle basé sur l’apprentissage automatique pour l’optimisation auto-
matique de programmes.
Figure 11: La base de données d’apprentissage pour l’optimisation automatique des programmes.
17
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
Figure 12: L’ensemble des propriétés qui caractérisent les programmes de l’ensemble d’apprentis-
sage dans la technique d’Agakov [Agakov et al., 2006].
Figure 13: L’ensemble des optimisations appliquées aux programmes de l’ensemble d’apprentis-
sage, dans la technique d’Agakov [Agakov et al., 2006].
18
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
Les SVM (Machines à vecteur de support) ont été employées pour établir un modèle
qui décide de la pertinence d’application d’une optimisation oi sur le programme P [Park
et al., 2011]. La sortie de ce modèle est l’une des deux classes "vrai" ou "faux" : "vrai"
pour dire que l’optimisation oi est bénéfique pour P, "faux" sinon.
Les réseaux de neurones ont été utilisés pour prédire le temps d’exécution d’un
programme C/C++ optimisé par un tuilage à 3 niveaux au maximum. Chaque facteur de
tuilage peut prendre une valeur parmi 22 facteurs de tuilage différents, donc l’espace des
optimisations est de : 223 = 10648 combinaisons. Parmi ces combinaisons, un ensemble
de 530 combinaisons des facteurs de tuilage sont choisies aléatoirement, dont 90% sont
utilisées pour l’apprentissage et 10% pour le test de validité du modèle. Pour chaque
programme à optimiser, on génère un modèle à base de réseau de neurones. Le réseau
de neurones développé est constitué de 3 couches seulement : la couche d’entrée avec 3
neurones, la couche de sortie avec un seul neurone et une couche intermédiaire avec 30
neurones. Le modèle reçoit en entrée 3 entiers qui sont les facteurs de tuilage (T1, T2,
T3) à appliquer sur le programe P et il renvoit en sortie le temps d’exécution estimé de
ce programme optimisé par (T1, T2, T3) [Rahman et al., 2010].
19
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
Tableau I: Tableau comparatif entre les approches d’optimisation automatique utilisées dans les
compilateurs
Critère /
Automatisation Complexité de conception Temps de calcul
Approche
Approche
Automatique Facile Exponentiel
exploratrice
Approche
Automatique Difficile Très réduit
analytique
Plus petit que celui
Moins difficile
Approche prédictive Automatique de l’exploratrice plus grand
que l’analytique
que celui de l’analytique.
tout dépend de la
Moins difficile que celle
Approche AA Automatique taille de l’ensemble
de l’analytique
d’apprentissage
20
Chapitre II. Approches d’optimisation automatique pour les compilateurs.
Le modèle reste toujours difficile à concevoir ; c’est alors que l’approche basée sur
l’apprentissage automatique a vu le jour. Cette approche cherche à générer le modèle
de façon automatique à partir d’une base de données d’apprentissage constituée de pro-
grammes pré-optimisés. Le modèle généré sera capable ensuite d’optimiser de nouveaux
programmes ne figurant pas dans la base de données d’apprentissage. Mais, cette approche
a ses propres contraintes parmi lesquelles on trouve la difficulté du choix des caractéris-
tiques du programmes, et l’algorithme d’apprentissage automatique appliqué.
II.7 Conclusion
Nous avons abordé dans ce chapitre les différentes approches utilisées pour l’automa-
tisation du processus d’optimisation dans les compilateurs, en exposant le principe de
chacune d’elles, et énumérant les challenges rencontrés à l’emploi de chaque approche.
21
Chapitre III
III.1 Introduction
Halide est un nouveau langage et compilateur apparu en 2012, embarqué sur le C++ et
spécifique au domaine du traitement d’images, qui a découplé la définition de l’algorithme,
de ses optimisations (schedule) pour produire un code lisible dans lequel les optimisations
n’affectent pas le fonctionnement et les sorties de l’algorithme [Ragan-Kelley et al., 2012].
L’application des optimisations sur les programmes écrits dans un langage généraliste,
comme le C ou Java, peut nuire à son fonctionnement, le rendre illisible et difficilement
maintenable. Contrairement à Halide, où on peut parcourir plusieurs combinaisons d’op-
timisations sans perturber le fonctionnement du code.
Par exemple, F(x,y) = x+y est une instruction Halide qui manipule une seule fonction
F et qui prend en paramètre les variables x et y. F est équivalente à une boucle imbriquée
de profondeur 2 indexée par les variables x et y (voir le pseudo-code de la figure 14).
Les algorithmes de traitement d’images sont caractérisés par des pipelines profonds
où chaque étage (fonction) implémente un traitement d’images spécifique. En effet, un
algorithme de traitement d’images en Halide n’est pas constitué d’une seule fonction mais
1. Dans ce rapport, la variable et le niveau de boucle d’une fonction Halide veulent dire la même chose.
22
Chapitre III. Halide et ses techniques d’optimisation automatique.
Par exemple, le traitement qui consiste à flouter une image nécessite trois fonctions
Halide pour l’implémenter : la fonction input pour référencer l’image en entrée, la fonction
Blurx pour référencer l’image après la première opération de floutage et en dernier la
fonction Blury pour référencer l’image après la seconde opération de floutage. L’exemple
de la figure 15 montre le code Halide équivalent au traitement de floutage.
La première ligne du code calcule la fonction Blurx qui présente la moyenne de chaque
trois pixels adjacents de l’image input. Quant à la seconde ligne du code, elle calcule la
fonction Blury qui est à son tour définie par la moyenne de chaque trois pixels adjacents
de l’image Blurx. Input est productrice pour Blurx qui est à son tour consommatrice et
Blurx est productrice pour Blury qui est à son tour consommatrice. L’algorithme Halide
de la figure 15 implémente le pipeline de traitement d’images exposé dans la figure 16.
La partie algorithme ne décrit que les traitements qui doivent être réalisés par le
programme, et ne spécifie aucun ordre sur l’évaluation de chaque fonction du pipeline. On
parle alors d’une implémentation naïve, où le calcul des valeurs des fonctions se fait en
mode inline (calcul par substitution de fonctions) et aucune optimisation n’est appliquée
sur le programme. Pour optimiser un programme Halide, l’utilisateur doit introduire des
optimisations au niveau de la partie schedule 3 .
2. Un étage producteur est utilisé pour le calcul d’un autre étage qu’on appelle consommateur à son
tour.
3. Planning en français, nous optons pour l’appellation schedule tout au long de ce rapport.
23
Chapitre III. Halide et ses techniques d’optimisation automatique.
Figure 17: Optimisations dans Halide (le nom des optimisations et leur équivalent dans le langage
Halide).
24
Tableau II: Tableau récapitulant les optimisations Halide qui sont appliquées sur un seul étage
Optimisation
Paramètres Signication Equivalent
Halide
dans le jargon
Changer l’ordre des niveaux
- f : la fonction de boucle de f en commen-
f.reorder(x,y) - x et y : deux niveaux çant par la boucle la plus Interversion de
de boucle de f. interne x ensuite la plus boucle.
externe y.
- f : la fonction Paralléliser le niveau de bou-
f.parallel(y) Parallélisation
- y : un niveau de boucle de f. cle de f, dont l’indice est y.
de boucle.
- f : la fonction Vectoriser le niveau de bou-
f.vectorize(x) Vectorisation de
- x : un niveau de boucle de f. cle de f, dont l’indice est x.
boucle.
- f : la fonction Dérouler le niveau de boucle
f.unroll(x) Déroulage de
- x : un niveau de boucle de f. de f, dont l’indice est x.
boucle.
Fusionner les deux niveaux
- f : la fonction
de boucle de f, dont les indi-
- x, y : deux niveaux de boucle
f.fuse(x,y,t) ces sont x et y, pour former Coalescence de
de f.
une nouvelle boucle dont boucle.
- t : nouveau niveau de boucle
l’indice est t.
Éliminer le niveau de boucle
- f : la fonction.
x et produire deux nouveaux
- x : niveau de boucle de f.
f.split(x,xo,xi,f ) niveaux de boucle dans cet Découpage en
- xi, xo : deux nouveaux ni-
ordre : xo, xi. Où l’étendue bande.
veaux de boucle pour f.
de xi est f1.
- f : la fonction. Éliminer x et y et produire
- x, y : deux niveaux de boucle de nouveaux niveaux de
f.tile(x,y,xo,yo,xi,yi,f1,f2) de f. boucle dans cet order : xo,yo Tuilage de
- xi, yi, xo, yo : les indices xi,yi. Où l’étendue de xi est boucle.
25
Chapitre III. Halide et ses techniques d’optimisation automatique.
Pour spécifier la granularité de calcul d’une fonction productrice f par rapport à une
fonction consommatrice g qui a comme variables : x et y, on choisit entre : f.compute_at(g,x),
f.compute_at(g,y), f.compute_root().
D’une part, plus la granularité de stockage est fine pour une fonction productrice f
par rapport à sa fonction consommatrice g, plus le programme aura tendance à faire du
26
Chapitre III. Halide et ses techniques d’optimisation automatique.
calcul redondant. En effet, les valeurs de f qui ont été déjà calculées mais non stockées
seront recalculées une fois que g en aura besoin une autre fois. Par contre, une granularité
de stockage fine assure que le buffer qui contient les valeurs de f tient dans le cache (et
c’est ce qui améliore la localité). D’autre part, plus la granularité de stockage est grande,
moins le programme fera de calcul redondant car les anciennes valeurs de f sont toujours
présentes dans le buffer alloué. Cependant, en allouant un buffer de taille importante,
il risque de ne pas tenir dans le cache (on risque de faire plusieurs transferts mémoire
principale - mémoire cache pour récupérer les données stockées) [Allen and Kennedy,
2002, Ragan-Kelley, 2014].
– L’optimisation de fusion n’est pas considérée car elle n’est pas aussi bénéfique pour
le programme.
– Les étendues des boucles du programme et la taille des blocs ont été restreint à
des multiples de deux et générés aléatoirement car elles ont été considérées plus
bénéfiques pour le programme.
– Les niveaux de boucle de petite étendue ne sont pas tuilés car il a été considéré que
le tuilage n’était pas bénéfique pour les boucles qui ont déjà une petite étendue.
– Les niveaux de boucle dont l’étendue est inconnue se voient affectés des bornes
maximales et minimales aléatoires pour ne pas tester toutes les étendues de boucle
possibles pour chaque fonction du programme.
– L’individu : est considéré comme un schedule dont la fitness est son temps d’exé-
cution une fois qu’il est appliqué sur le programme. Un individu est une séquence
de chromosomes représentée par l’ensemble des optimisations qui figurent dans le
schedule (voir figure 19).
27
Chapitre III. Halide et ses techniques d’optimisation automatique.
– La population initiale est générée en affectant des schedules dits raisonnables (dans
l’annexe D.2 nous expliquons ce qu’est un schedule raisonnable et comment le trou-
ver), de façon stochastique à toutes les fonctions de l’algorithme.
– La sélection des parents se fait une fois que la population initiale générée. Deux
schedules de la population, dits schedules parents sont sélectionnés en utilisant la
stratégie de sélection par tournoi.
– Le croisement de deux parents est effectué pour générer deux nouveaux individus
(schedules), qu’on appelle : schedules enfants. Au cours du croisement, deux points
de chaque schedule parent sont choisis aléatoirement, on leur échange les optimi-
sations (chromosomes) qui se trouvent entre ces deux points, pour former les deux
individus enfants.
– La mutation : Une opération appliquée sur les nouveaux individus afin de les diver-
sifier. En premier une fonction f est choisie aléatoirement à partir du programme.
Ensuite, une opération de mutation parmi huit possibilités de mutation est appliquée
sur cette fonction.
Pour juger de son efficacité, cet autotuner a été appliqué sur un ensemble de pro-
grammes de traitement d’images, afin de les optimiser de façon automatique (voir le ta-
bleau III). Ensuite, le temps d’exécution de chaque programme optimisé par l’autotuner
est comparé avec celui du programme optimisé à la main par des experts. La comparaison
se fait en calculant l’accélération (le speedup) qui est donnée par la formule :
28
Chapitre III. Halide et ses techniques d’optimisation automatique.
Tableau III: Benchmarks optimisés par l’autotuner et les résultats obtenus sur une machine de
type quad core Xeon W3520 x86 CPU
L’utilisateur doit donner en entrée pour OpenTuner : l’espace de recherche qui est mo-
délisé par un ensemble de paramètres, une fonction qui calcule la fonction objectif pour
toute solution candidate et les techniques d’exploration utilisées pour explorer l’espace de
recherche.
29
Chapitre III. Halide et ses techniques d’optimisation automatique.
Tableau IV: Tableau récapitulant les paramètres OpenTuner correspondants aux optimisations
Halide
La technique a accéléré les benchmarks optimisés à la main avec une accélération entre
1 et 1.5 seulement mais cela est valable sur 3 différentes architectures. Contrairement, à
la première technique basée sur l’autotuner génétique qui n’a été exécutée que sur une
seule architecture d’exécution. Mais qu’en est-il pour les autres architectures d’exécution ?
peut-être que l’autotuner génétique ne pourra pas générer d’aussi bons résultats que ceux
qu’il a généré sur l’architecture de test.
30
Chapitre III. Halide et ses techniques d’optimisation automatique.
Tableau VI: Tableau comparatif entre l’algorithme génétique et la méthode basée sur
OpenTuner pour la génération automatique de schedules pour un programme Halide
31
Chapitre III. Halide et ses techniques d’optimisation automatique.
Figure 21: Pipeline de traitement d’images dont les étages sont groupés par
l’Auto-scheduler.
32
Chapitre III. Halide et ses techniques d’optimisation automatique.
– Une bonne taille de tuilage : le facteur de tuilage interne doit être assez petit et
multiple de la taille des registres vectoriels pour vectoriser. Le facteur de tuilage
externe est assez grand pour bénéficier du parallélisme et assez petit pour qu’une
tuile puisse tenir dans le cache.
– L’estimation du gain d’une fusion potentielle : Soient deux groupes (g1, g2) où g1
est fusionné potentiellement dans g2 avec f la fonction de sortie de g2. Le coût de
la fusion est donné par la formule :
5. voir annexe D.3 pour plus de détails sur comment calculer le coût arithmétique d’une fonction.
33
Chapitre III. Halide et ses techniques d’optimisation automatique.
Tableau VII: Tableau comparatif entre les méthodes des deux approches : analytique et exploratrice
N’importe quel
Environnement d’exécution Le même que celui du programme
environnement d’exécution
Exécute et calcule le temps Estime la performance de
Choix d’optimisation d’exécution du programme l’optimisation sur le
avec l’optimisation programme.
Efficace lors d’une recherche
Efficacité du modèle
Efficacité du système exhaustive ou une heuri-
implique sa complexité
stique bien paramétrée
Plus petit que celui de
Espace de recherche Généralement très grand l’approche exploratrice, car
il est guidé par le modèle.
La méthode analytique peut être exécutée sur une architecture A afin d’élaborer des
schedules optimaux pour une architecture B. En effet, il suffit d’introduire les paramètres
de l’architecture B en entrée du modèle au lieu des caractéristiques de l’architecture A.
Donc, la méthode n’est pas forcément exécutée sur le même environnement d’exécution
que celui pour lequel on optimise les programmes. Contrairement aux deux techniques ex-
ploratrices, qui compilent et exécutent les combinaisons d’optimisations sur l’architecture
d’exécution, elles doivent s’exécuter sur la même architecture pour laquelle on optimise
les programmes.
III.7 Conclusion
Halide est un nouveau langage qui commence à gagner de l’ampleur grâce à sa sépara-
tion entre l’algorithme et ses optimisations et la simplicité d’expression des optimisations.
Dans ce chapitre, nous avons établi un état de l’art sur les travaux d’optimisation au-
tomatique pour le compilateur Halide. En effet, il existe trois travaux dans la littérature,
dont deux reposent sur l’approche exploratrice et une technique repose sur l’approche
analytique. Ces techniques ont été testées de façon particulière sur des programmes de
traitement d’images. Néanmoins, la technique analytique a été appliquée sur divers pro-
grammes comme la multiplication de matrice, la convolution et d’autres.
34
Chapitre III. Halide et ses techniques d’optimisation automatique.
Dans le chapitre suivant, nous arrivons à la partie contribution. Dans cette partie,
nous expliquons la méthode proposée pour l’optimisation automatique d’une classe des
programmes Halide.
35
Deuxième partie
Contributions
Chapitre IV
Conception
IV.1 Introduction
Ce projet de fin d’études fait partie des projets initiés par l’équipe de recherche Com-
mit du MIT. Le but global des projets de cette équipe est de développer des langages
qui permettent d’exprimer les programmes de façon simple et de pouvoir les optimiser
de façon automatique sur tout type d’architecture. L’un des compilateurs développés par
Commit est le compilateur Halide.
Notre travail consiste à mettre en place un système qui génére pour tous les pro-
grammes de la classe des RNC, un schedule compétitif à celui trouvé par un expert en
matière d’optimisation de programmes et cela dans un délai inférieur à 24 heures.
Dans ce chapitre, après avoir décrit le problème, nous présentons notre solution. Cette
solution est répartie en deux sections : la conception globale du système et la conception
détaillée de la stratégie de construction des schedules.
1. réseaux de neurones convolutifs, voir annexe G pour plus d’informations sur les réseaux de neurones.
37
Chapitre IV. Conception
Tableau VIII: Modélisation de l’entité Schedule par un tableau d’entités, où chaque entité repré-
sente un type d’optimisation appliquée sur l’algorithme A.
T ile1 ... T ilei Split1 ... Splitj Reorder ... Fuse ... Parallel ... Vectorize ... Unroll ... compute_at ... store_at ...
38
Chapitre IV. Conception
Le système utilise une méthode qui explore un certain espace de schedules pour un
algorithme Halide et qui renvoie à la fin du traitement le meilleur schedule rencontré.
Pour ce faire, l’utilisateur introduit d’abord le code source de l’algorithme Halide, ainsi
39
Chapitre IV. Conception
qu’un fichier d’annotation pour annoter l’algorithme et renseigner son contenu comme :
fonctions, variables, constantes . . . etc.
Selon la stratégie d’exploration de l’espace des optimisations, nous construisons alors
un sous-ensemble de schedules candidats. Chaque schedule S construit doit être évalué.
Suite à l’évaluation de S, nous le stockons ainsi que sa valeur d’évaluation dans une
base de données. La sauvegarde dans la base de données est une préparation pour la
phase d’apprentissage automatique où elle (la base de données) va constituer une base
d’apprentissage pour la construction d’un modèle qui optimise les programmes de façon
automatique. Lorsque la méthode s’arrête, elle renvoie le meilleur schedule trouvé ainsi
que son temps d’exécution 2 .
Figure 24: Test de performance d’un schedule construit sur le programme Halide en entrée.
40
Chapitre IV. Conception
– la liste des fonctions auxquelles elle fait appel (ses fonctions productrices).
Une variable est une entité caractérisée par :
– son nom,
– son étendue,
– son type.
Une optimisation dispose de ses propres caractéristiques (qui dépendent de sa nature).
Néanmoins, chaque optimisation est appliquée sur une certaine fonction du programme,
donc chacune est caractérisée par au moins la fonction sur laquelle elle est appliquée.
Ci-dessous les caractéristiques de chaque type d’optimisation :
ii. Un découpage en bandes est caractérisé par la variable sur laquelle l’optimisation
est appliquée, auquel s’ajoute le facteur de découpage en bande utilisé.
iii. Une interversion de boucle est caractérisée par la liste des variables sur lesquelles
elle est appliquée.
iv. Un tuilage est caractérisé par les deux variables sur lesquelles il est appliqué, et les
deux facteurs de tuilage utilisés.
41
Chapitre IV. Conception
v. Une fusion est caractérisée par les deux variables sur lesquelles l’optimisation est
appliquée.
vi. Une granularité de calcul/ stockage est caractérisée par la fonction consommatrice
dans laquelle la fonction productrice est calculée/ stockée et le niveau de boucle
(la variable) de la fonction consommatrice où le calcul/ stockage de la fonction
productrice est réalisé.
Cette caractérisation nous a permis de définir la structure du fichier d’annotation que
l’utilisateur introduit à l’entrée du système.
Par exemple, pour l’algorithme Halide A de la figure 25, nous devons avoir le fichier
d’annotation exposé dans la figure 26.
Tout fichier d’annotation doit respecter cette syntaxe et doit être rempli rigoureuse-
ment. Un fichier d’annotation qui respecte cette syntaxe mais qui est a été rempli par
de fausses informations va fausser les schedules construits car la stratégie de construction
des schedules ne saura pas la vraie structure de l’algorithme à optimiser.
42
Chapitre IV. Conception
Nous présentons dans la figure 27 un schéma rassemblant les différentes entités sau-
vegardées et luers relations. Nous avons opté pour une base de données non-relationnelle
(NoSQL) car son schéma est facilement extensible, qui nous permet de sauvegarder des
tableaux et des entités imbriquées dans la même table.
Tous ces avantages nous les avons exploités pour pouvoir sauvegarder différents types
d’optimisations dans la même table, et étendre le schéma de la table des optimisations à
chaque nouvelle optimisation traitée. De plus, nous avons pu sauvegarder des champs de
type tableau dans les entités Programme et Schedule pour sauvegarder les fonctions qui
apparaissent dans le programme et les optimisations manipulées par le schedule respecti-
vement.
Chaque table de la base de données sauvegarde une entité du programme, avec ses
propres caractéristiques :
– Optimisation : est une table qui contient toutes les optimisations manipulées.
43
Chapitre IV. Conception
– id_variables : tableau contenant les identifiant des variables concernées par une
interversion de boucle (s’il s’agit d’une optimisation d’interversion de boucles).
– Schedule : est une table dont chaque tuple représente un tableau d’optimisations.
– Fonction : est une table qui contient toutes les informations sur les fonctions mani-
pulées dans le programme
– Variable : est une table qui contient toutes les variables manipulées dans une fonc-
tion.
– Programme : est une table qui contient toutes les informations qui concernent le
programme ainsi que la liste des _id de ses fonctions.
44
Chapitre IV. Conception
Figure 28: Méthodes conçues pour l’optimisation automatique des programmes Halide
1. Recherche exhaustive : Nous avons commencé par développer une méthode qui
explore toutes les combinaisons d’optimisations applicables à un programme. Mais
cette méthode s’est rapidement avérée prohibitive car le nombre de combinaisons à
explorer était trop grand.
2. Méthode Reorder-explore : A travers plusieurs exécutions de la méthode ex-
haustive, nous avons dégagé un comportement particulier qui concerne l’optimisa-
tion d’interversion de boucle. Ce comportement nous a permis de réduire le nombre
de combinaisons à tester et de décomposer l’étage d’exploration des optimisations
en deux. Donc au lieu d’explorer toutes les optimisations possibles en même temps,
la méthode Reorder-explore agit en deux étapes d’exploration. Premièrement, elle
explore différentes valeurs pour l’optimisation d’interversion de boucle, sans inclure
aucune des autres optimisations et à la fin de cette étape elle retient la meilleure
interversion parcourue. Deuxièment, elle explore de façon exhaustive les autres op-
timisations sauf l’optimisation d’interversion de boucle (qui n’est pas explorée car
nous lui affectons l’interversion qui a été retenue lors de la première étape). Cette
nouvelle stratégie a réduit considérablement l’espace des schedules à explorer, mais
il reste toujours grand et prohibitf à explorer.
3. Méthode Reorder-analytique : La méthode Reorder-analytique reprend les mêmes
étapes de Reorder-explore avec une modification sur la première étape d’exploration.
Lors de la première étape, on explore plusieurs possibilités d’interversions pour dé-
cider de la meilleure. L’exploration des possibilités d’interversion s’avère assez coû-
teuse, par exemple, pour une fonction à 5 variables, on a 5 ! = 120 possibilités
d’interversion de boucle. Dans le but de réduire ce nombre de tests, la méthode
Reorder-analytique utilise le modèle analytique exposé dans [Allen and Kennedy,
2002] pour le choix d’une bonne interversion de boucle. Ce modèle vient rempla-
cer la première étape d’exploration présentée dans Reorder-explore. Mais il demeure
prohibitif d’explorer les autres combinaisons d’optimisations de façon exhaustive (la
seconde étape de la méthode Reorder-explore).
4. Méthode HalideAutotuner : La méthode HalideAutotuner représente la der-
nière stratégie de construction de schedules que nous proposons pour l’optimisation
automatique des programmes Halide. Elle utilise plusieurs stratégies d’exploration
d’espace et un modèle pour la construction des schedules de bonne qualité et dans
les temps impartis. En effet, elle utilise le modèle analytique de [Allen and Kennedy,
2002] pour le choix d’une bonne interversion de boucle. Elle explore l’optimisation
de déroulage de façon exhaustive. Elle ne considère pas les optimisations de coa-
lescence de boucle et celle de la granularité de stockage. Elle applique toujours les
optimisations de vectorisation et de parallélisation. En dernier, elle utilise deux va-
riantes du Hill Climbing pour les optimisations de type granularité de calcul, tuilage
et découpage en bandes.
Dans la section suivante, nous présentons en détails le processus que nous avons suivi
pour l’optimisation automatique des programmes Halide.
45
Chapitre IV. Conception
Nous avons développé pour chaque type d’optimisation une méthode exhaustive basée
sur le backtracking récursif pour construire toutes ses combinaisons possibles. A chaque
fois qu’une combinaison d’une certaine optimisation est construite, nous passons à la
seconde optimisation et ainsi de suite (voir figure 29). Lorsque nous arrivons à la der-
nière optimisation, nous mesurons le temps d’exécution du schedule (voir l’annexe E.1
qui montre le diagramme de classe établi pour la recherche exhaustive).
Figure 29: Schéma résumant la construction des schedules dans l’exploration exhaustive.
46
Chapitre IV. Conception
naisons d’optimisations, nous avons eu plusieurs discussions avec des experts pour nous
indiquer les optimisations qui sont généralement bénéfiques et celles qui sont mauvaises
pour un programme. Nous sommes arrivés aux constatations suivantes :
– Le découpage en bande est appliqué uniquement pour les niveaux de boucle à vec-
toriser ou dérouler.
– Les facteurs de découpage en bande et ceux du tuilage sont des puissances de deux
uniquement.
– Le déroulage peut être appliqué sur les deux boucles les plus internes ou la plus
interne uniquement.
Pour adapter notre système à ces constatations, nous avons introduit la notion de
restriction. Par le biais d’une restriction, nous pouvons désactiver une optimisation lors
de l’exploration ou restreindre les valeurs qu’elle peut prendre (à l’exemple des facteurs
de tuilage et de découpage en bandes), ou même fixer sa valeur à une valeur prédéfinie.
Suite aux restrictions présentées ci-dessus, et pour une fonction à 2 niveaux de boucle
dont chacun a une étendue de 32, nous comptons une réduction de l’ordre de 99.80% pour
l’espace des combinaisions d’optimisations.
47
Chapitre IV. Conception
Figure 30: Courbe montrant la différence entre l’application d’une bonne et d’une
mauvaise interversion de boucle sur les schedules renvoyés une fois l’interversion
fixée.
Suite à cette constation, nous avons proposé de décomposer l’exploration des opti-
misations en deux étapes. Premièrement, elle explore différentes valeurs pour l’optimisa-
tion d’interversion de boucle, sans inclure aucune des autres optimisations, et retient la
meilleure interversion parcourue. Deuxièment, elle explore de façon exhaustive les autres
optimisations sauf l’optimisation d’interversion de boucle qui est fixée à celle retenue lors
de la première étape (voir figure 31 et algorithme 2).
48
Chapitre IV. Conception
49
Chapitre IV. Conception
Figure 32: Introduction d’un modèle analytique pour le choix de l’optimisation d’interversion de
boucle dans Reorder-Analytique
Ce modèle vise à trouver une bonne interversion de boucle à appliquer pour une boucle
imbriquée B = {B1 , B2 , ..., BN }, en estimant le nombre de défauts de cache engendrés
(le modèle est expliqué en détails au niveau de l’annexe F). Ce modèle donne en sortie
B’ ; un nouvel ordre aux boucles de B. Dans le but de tirer la meilleure optimisation
d’interversion pour une fonction Halide, nous avons adapté ce modèle analytique à notre
problème : chaque fonction est équivalente à une boucle imbriquée B, et les niveaux de
boucle Bi sont les variables de cette fonction.
Néanmoins, le modèle proposé par [Allen and Kennedy, 2002] peut construire une
optimisation d’interversion de boucle B’ qui n’est pas valide. Dans ce cas, il propose une
stratégie de correction pour construire une autre optimisation d’interversion de boucle
B" qui est proche de B’ mais qui est valide. Cette stratégie consiste à appliquer des
permutations sur les niveaux de boucle de B, en essayant d’approcher l’interversion B’ sans
causer d’invalidité. Au cours de ces permutations, le modèle de [Allen and Kennedy, 2002]
vérifie la validité de chaque permutation en utilisant la théorie de l’analyse de dépendance.
Dans notre cas, nous avons intégré cette stratégie de correction, mais comme Halide
n’implémente pas les algorithmes d’analyse de dépendance, nous avons testé la validité
de chaque permutation en compilant et exécutant le code avec l’interversion permutée.
Ensuite chaque interversion permutée est sauvegardée avec son temps d’exécution dans un
tableau de candidats. Lorsque la stratégie de correction arrive à sa fin, nous choisissons la
permutation avec le plus petit temps d’exécution parmi celles qui figurent dans le tableau
des candidats.
50
Chapitre IV. Conception
51
Chapitre IV. Conception
– Le modèle analytique de [Allen and Kennedy, 2002] pour le choix d’une bonne
interversion de boucle.
– Une exploration exhaustive pour l’optimisation de déroulage.
– Une exploration en Hill Climbing pour l’optimisation de granularité de calcul (com-
pute_at).
– Une exploration en Hill Climbing pour les facteurs de tuilage et de découpage en
bandes.
Les optimisations de parallélisation et de vectorisation sont directement appliquées
à tous les schedules testés. Quant à l’optimisation de fusion et celle de la granularité
de stockage, elles n’ont pas été appliquées sur les schedules (voir annexe E.2 pour la
conception du diagramme de classe de HalideAutotuner).
IV.4.4.1 Explication des choix des stratégies d’exploration pour chaque type
d’optimisation
Chaque optimisation se distinguait des autres de par son comportement, et son impact
sur la performance du programme. De ce fait, nous avons utilisé une stratégie d’explora-
tion pour chaque optimisation. Dans cette partie, nous allons justifier le choix de chaque
stratégie d’exploration adoptée.
1. Modèle analytique pour l’optimisation reorder : Le choix de ce modèle a été
expliqué dans la section IV.4.3.
2. Absence d’exploration pour les optimisations parallel et vectorize : La
parallélisation et la vectorisation sont bénéfiques pour un programme, donc nous les
appliquons directement sur le schedule (information transmise par des experts en
optimisation de programmes).
3. Absence d’exploration pour les optimisations fuse et store_at : Après plu-
sieurs exécutions, nous avons constatés que ces deux optimisations n’impactent pas
considérablement la performance d’un programme, donc elles ne sont pas appliquées
sur les schedules.
52
Chapitre IV. Conception
Figure 33: Evolution de la performance d’un schedule en faisant varier uniquement l’optimisation
compute_at.
53
Chapitre IV. Conception
Figure 34: Variante du Hill Climbing pour trouver une optimisation de granularité de calcul
(compute_at) de bonne qualité
2. Variante du Hill Climbing pour les facteurs de tuilage et de découpage : Le choix des
facteurs de tuilage et de découpage en bandes affecte considérablement la qualité des
schedules. En effet, les facteurs de découpage sont critiques pour les optimisations de
54
Chapitre IV. Conception
déroulage et de vectorisation. Quant aux facteurs de tuilage, ils sont critiques pour
l’amélioration de l’utilisation de la mémoire cache. Une exploration exhaustive des
facteurs de ces deux optimisations n’est pas possible, car cela va engendrer un grand
espace de schedules à tester. Alors, nous avons mis en place une variante du grimpeur
(dont le principe ressemble à celui du Hill Climbing appliqué à compute_at) pour
retrouver rapidement des facteurs d’assez bonne qualité (voir figure 35).
Figure 35: Variante du Hill Climbing pour la recherche de bons facteurs de découpage en bandes
et de tuilage
55
Chapitre IV. Conception
généralement de bonnes performances (16 ou 32 par exemple). Rajoutant à ça, les opti-
misations parallel (ligne 14 et 15 de l’algorithme 4) et vectorize (ligne 11 de l’algorithme
4) qui sont appliquées sur toutes les fonctions car elles sont considérées bénéfiques.
Suite à cette initialisation de S, nous enchaînons avec une exploration des optimisations
de type unroll et des optimisations de type compute_at avec une recherche exhaustive
et une exploration en Hill Climbing respectivement (ligne 16 de l’algorithme 4). Durant
cette exploration, nous allons être soumis à tester plusieurs schedules qui diffèrent uni-
quement selon les valeurs que prennent les optimisations unroll et compute_at. Une fois
cette exploration terminée, nous allons retenir les 10 meilleurs schedules explorés (ligne
17 de l’algorithme 4).
56
Chapitre IV. Conception
IV.5 Conclusion
Dans la partie conception, nous avons commencé par décrire le problème et ses compo-
santes. En effet, ce travail consiste à optimiser automatiquement la classe des programmes
implémentant les RNC et produire des programmes dont le temps d’exécution est proche
de celui des programmes optimisés à la main. Cela doit être réalisé dans un délai qui ne
dépasse pas les 24 heures. En second lieu, nous avons présenté l’architecture globale de
notre système et ses différents modules qui sont répartis entre : identification de la struc-
ture du programme, construction des schedules, leur évaluation et la sauvegarde dans la
base de données.
En dernier, nous nous sommes focalisés sur la méthode de construction des schedules
développée HalideAutotuner. Mais avant d’arriver à HalideAutotuner et son principe de
fonctionnement, nous avons mis en relief tout le processus parcouru pour arriver à la
conception de cette méthode. En effet, nous avons commencé par une exploration exhaus-
tive dans tout l’espace des optimisations applicables à un programme. Au fur et à mesure,
nous avons réduit l’espace de recherche des combinaisons d’optimisations en introduisant
un modèle analytique et des stratégies d’exploration d’espace approchées (comme le Hill
Climbing) jusqu’à arriver à une méthode qui satisfait nos besoins qui sont : la construction
d’un schedule de bonne qualité dans un temps inférieur à 24 heures pour les programmes
implémentant les RNC.
57
Chapitre IV. Conception
58
Chapitre V
Réalisation
V.1 Introduction
Dans le chapitre précédent, nous avons présenté notre heuristique et son principe de
fonctionnement en utilisant des diagrammes UML. Suite à une série de tests et évalua-
tions, nous avons identifié des techniques de recherche efficaces qui nous font converger
rapidement vers de bon schedules, de ce fait, nous sommes arrivés à proposer une bonne
méthode qui équilibre entre son temps d’exécution et la qualité des schedules qu’elle ren-
voit.
Dans cette partie - implémentation, nous allons décrire l’architecture globale du sys-
tème qui nous permet d’optimiser de façon automatique chaque programme de la classe
des RNC sur une architecture cible de type CPU, dans un temps relativement petit (moins
de 24 heures). De plus, nous allons cité toutes les technologiques, outils et bibliothèques
utilisées pour le développement de notre solution, en justifiant le choix de chacun d’eux.
59
Chapitre V. Réalisation
60
Chapitre V. Réalisation
– JSON : Nous avons choisi JSON (JavaScript Object Notation) comme format pour
le fichier d’annotation. En effet, JSON est lisible, facilement interprété par la ma-
chine et par l’humain. Il se base sur des couples de données de type ’clé : valeur’, où
la valeur peut varier d’une simple donnée atomique à une liste de données ordonnée.
– Numpy : C’est une bibliothèque Python qui permet de réaliser du calcul algébrique
(sur des vecteurs ou des matrices). Nous l’avons utilisé pour implémenter le modèle
analytique exposé dans IV.4.3 afin de calculer les estimations de défaut de cache.
– Scipy : C’est une bibliothèque Python qui est utilisée dans le calcul mathématique
et scientifique, qui utilise les vecteurs et matrices de type Numpy. Nous avons uti-
lisé Scipy pour la maximisation d’une fonction mathématique pour implémenter le
modèle analytique exposé dans IV.4.3 afin de calculer les estimations de défaut de
cache.
– Sympy : C’est une bibliothèque Python utilisée dans le calcul formel d’expression
mathématique. Nous avons utilisé Sympy pour traiter les expressions mathématiques
manipulées par les fonctions Halide.
61
Chapitre V. Réalisation
Figure 38: Ligne de commande pour lancer la méthode de construction des schedules HalideAu-
totuner.
• - -heuristique-limit : donne le temps maximal pris par l’heuristique. C’est à dire, dès
que l’heuristique dépasse les - -heuristique-limit secondes, elle renvoit le meilleur
schedule exploré et s’arrête.
V.5 Conclusion
Dans ce chapitre, nous avons argumenté tout choix technologique pour l’élaboration
des trois méthodes du système qui visent toutes à optimiser de façon automatique une
classe de programme qui implémentent les RNC écrite en Halide. Nous avons présenté
62
Chapitre V. Réalisation
63
Chapitre VI
Tests et évaluations
VI.1 Introduction
Après avoir présenté notre système et détaillé son architecture technique, nous allons
l’évaluer. Rappelons que notre système consiste à optimiser de façon automatique la classe
des programmes implémentant les RNC sur une architecture à base de CPU. Cela est réa-
lisé dans l’exigence que le temps d’exécution des programmes optimisés automatiquement
(par HalideAutotuner) s’approche du temps d’exécution des programmes optimisés à la
main par des experts en matière d’optimisation de programmes.
64
Chapitre VI. Tests et évaluations
Les benchmarks sont classés du plus simple au plus complexe (voir tableau X), et
cela en fonction du nombre de fonctions qui apparaissent dans chaque benchmark. Plus le
nombre de fonctions d’un programme est grand, plus il est complexe à optimiser. En effet,
un programme qui renferme plus de fonctions qu’un autre aura plus de combinaisons d’op-
timisations applicables et donc un plus grand espace de recherche. Les deux benchmarks
les plus simples à optimiser sont ReLU et maxpool. Chacun ne compte que 2 fonctions
dont une seule est consommatrice. Cependant, le programme de la multiplication matri-
cielle par lots, qui est assez complexe à optimiser, renferme 8 différentes fonctions, dont
4 consommatrices.
Afin d’optimiser un programme de façon automatique dans notre système, et pour une
taille d’entrée particulière, il faut qu’il soit annoté. En effet, avant de procéder à l’optimi-
sation automatique de nos benchmarks, nous les avons tous annotés manuellement, selon
leurs tailles d’entrée, dans un fichier d’annotation qui contient toutes les informations
importantes sur le programme.
65
Chapitre VI. Tests et évaluations
schedule trouvé par le système est de bonne qualité si son accélération est supérieure
ou égale à 90%. On rappelle la formule de l’accélération :
temps d’exécution du programme optimisé à la main
Acc = (VI.1)
temps d’exécution du programme optimisé par le système.
Tous les benchmarks ont été optimisés sur la même architecture à base de CPU. Cela
est réalisé afin de donner les bonnes combinaisons d’optimisations en se référant à une seule
architecture d’exécution, et pour pouvoir effectuer des comparaisons entre les différents
benchmarks.
Nous avons déployé notre système sur ce cluster, et nous l’avons utilisé pour optimi-
ser les 22 instances de test. L’utilisation du cluster nous a permis d’optimiser plusieurs
instances en même temps ; chaque programme avec une taille d’entrée particulière est
66
Chapitre VI. Tests et évaluations
67
Chapitre VI. Tests et évaluations
Figure 42: Fichier d’annotation pour le programme de convolution pour des entrées de 32 filtres
de taille 5*5*16 et 32 images de taille 68*68*16 chacune.
68
Chapitre VI. Tests et évaluations
Pour chaque taille d’entrée, nous avons mesuré le temps d’exécution du programme
sans optimisations (exe-Naïf), le temps d’exécution du programme optimisé à la main (exe-
Manuel), le temps d’exécution du programme optimisé en utilisant le schedule trouvé par
HalideAutotuner (exe-Autotuner) et le temps pris par le système pour l’optimisation du
programme (TS). Ensuite, nous calculons l’accélération (AccM ) du programme optimisé
par HalideAutotuner par rapport à celui optimisé manuellement et l’accélération (AccN )
du programme optimisé par notre système par rapport à celui qui n’a pas été optimisé
(voir tableau XIII).
Nb.
Les entrées exe-Naïf exe-Manuel exe-Autotuner AccN AccM TS
schedules
- images : 68*68*16*32
1.6575 s 0.039635 s 0.018779 s 100.11 2.11 40 13min.
- filtres : 5*5*16*32
- images : 131*131*64*4
2.5581 s 0.075087 s 0.0236 s 108.39 3.18 44 43min.
- filtres : 3*3*64*64
- images : 260*260*16*32
22.7425 s 0.705296 s 0.193221 s 117.70 3.65 44 1h25min.
- filtres : 5*5*16*32
- images : 522*522*64*64
> 5000 s 51.6594 s 40.66 s > 132.53 1.24 69 6h03min.
- filtres : 11*11*64*64
69
Chapitre VI. Tests et évaluations
à un schedule de bonne qualité, avant les 24 heures pour toute instance de test du pro-
gramme de convolution. Nous remarquons que plus les tailles d’entrée du programme sont
larges, plus TS (temps pris par HalideAutotuner) est plus grand.
On remarque que la vectorisation est appliquée sur les schedules automatiques avec
des facteurs plus grands que ceux appliqués dans les schedules manuels. En effet, il est
connu que plus le facteur de vectorisation est grand, plus le traitement est parallélisé (voir
section I.2.2), donc on peut estimer que les facteurs de vectorisation ont participé dans
l’amélioration du temps d’exécution du programme.
Le déroulage n’est pas toujours bénéfique pour une boucle, surtout si son étendue
est grand. En effet, nous remarquons qu’au niveau du schedule manuel, le déroulage est
toujours appliqué sur la fonction conv.update() avec un facteur égal à la longueur du
filtre. Cependant, les schedules construits par notre système applique l’optimisation de
déroulage lorsque la longueur du filtre manipulé est petite (le cas de 3 et 5) et il n’est
pas appliqué lorsque la longueur du filtre est grande (le cas de 11). Donc, on peut estimer
que les facteurs de déroulage ont participé aussi à l’amélioration du temps d’exécution du
programme à optimiser.
Nous avons remarqué également que les schedules renvoyés par HalideAutotuner ap-
pliquent parfois des optimisations de type découpage en bandes, qui n’ont pas d’effet sur
le schedule, car ils ne sont ni vectorisés ni déroulés par la suite.
70
Tableau 43: Tableau comparatif entre les schedules construits automatiquement et manuellement en fonction de la taille des entrée pour le programme
de convolution
71
Chapitre VI. Tests et évaluations
Chapitre VI. Tests et évaluations
Figure 44: Fichier d’annotation pour le programme de relu avec des images de taille 64*64*32*32.
ReLU calcule en sortie un tableau à 4 dimensions. Dans ce cas, ce tableau a une taille de
64*64*32*32 (voir ligne 1 du fichier d’annotation) avec des données de type réel (voir ligne
3 du fichier d’annotation). Nous renseignons toutes les fonctions du programme (input
et relu) avec l’étendue de chaque variable qui apparaît dans la fonction. Par exemple,
la fonction relu dispose de quatre variables ordonnées dans vars, dont nous estimons
l’étendue dans estime. La fonction relu fait appel à la fonction input (ligne 10 du fichier
d’annotation) et son niveau de boucle vectorisable est x.
72
Chapitre VI. Tests et évaluations
Comme le programme ReLU n’est pas optimisé par des experts, nous l’avons nous-
mêmes optimisé manuellement. Pour aboutir à un schedule de bonne qualité, nous avons
suivi le raisonnement suivant :
– Nous n’avons pas appliqué le tuilage car il n’y avait pas de réutilisation de données.
– Comme l’accès aux données en mémoire était séquentiel pour les deux fonctions relu
et input, nous n’avons pas appliqué l’optimisation d’interversion de boucles.
– Nous avons testé la fusion plusieurs fois sur les deux niveaux de boucles les plus
externes.
– Nous avons appliqué la vectorisation sur le niveau de boucle x et nous avons fait
varié le facteur de vectorisation plusieurs fois.
Nb.
Les entrées exe-Naïf exe-Manuel exe-Autotuner AccN AccM TS
schedules
Le temps pris par HalideAutotuner pour optimiser les instances de l’algorithme ReLU
est petit relativement à celui pris pour l’optimisation des programmes de convolution.
Cela est dû à la taille de l’algorithme ReLU qui ne compte que quelques fonctions, et
donc un plus petit espace de recherche des combinaisons d’optimisations par rapport à
73
Chapitre VI. Tests et évaluations
celui de la convolution. De plus, ReLU est moins complexe donc son temps d’exécution
est plus petit (que ceux de la convolution) même en lui appliquant des combinaisons d’op-
timisations médiocres. Par exemple, le pire temps d’exécution que nous avons eu lors de
l’optimisation automatique de l’algorithme ReLU et avec la plus grande taille d’entrée
’256*256*32*32’ était de 0.092 secondes.
La technique d’optimisation automatique arrive à des schedules meilleurs que ceux dé-
veloppés manuellement avec une accélération supérieure à 2×, et améliore les algorithmes
ReLU naïfs avec une accélération allant de 3× jusqu’à 10×.
74
Chapitre VI. Tests et évaluations
Figure 45: Fichier d’annotation pour le programme de Convolution-ReLU avec des images de
taille 68*68*16*32 et des filtres de taille 5*5*16*32.
Nb.
Les entrées exe-Naïf exe-Manuel exe-Autotuner AccN AccM TS
schedules
- images : 68*68*16*32
1.67 s 0.050244 s 0.02663 s 62.98 1.88 386 2h05min.
- filtres : 5*5*16*32
- images : 131*131*64*4
2.55 s 0.081492 s 0.082745 s 30.91 0.98 460 2h25min.
- filtres : 3*3*64*64
- images : 260*260*16*32
23.04 s 0.587605 s 0.110015 s 209.46 5.34 1022 12h25min.
- filtres : 5*5*16*32
HalideAutotuner arrive à générer des schedules dont la qualité approche celle des
schedules développés par l’expert. Au niveau de la seconde ligne du tableau XVI, nous
remarquons que le schedule construit par HalideAutotuner est moins bon que celui dé-
veloppé par l’expert (accélération de 0.98 qui est inférieure à 1). Néanmoins, le schedule
développé par HalideAutotuner reste compétitif et proche de celui développé par l’expert.
75
Chapitre VI. Tests et évaluations
MaxPool représente une des couches d’un réseau de neurone convolutif qui sert à
réduire le nombre de neurones qui apparaissent dans la première couche (un neurone est
76
Chapitre VI. Tests et évaluations
équivalent à un pixel d’une image). Il sert à réduire le nombre de paramètres appris par
le modèle en réduisant la dimensionnalité du réseau de neurone.
Nb.
Les entrées exe-Naïf exe-Manuel exe-Autotuner AccN AccM TS
schedules
- images : 64*64*32*32
0.008967 s 0.007973 s 0.001407 s 6.37 5.66 16 4min.
- pool : 4*4
- images : 128*128*64*4
0.008385 s 0.005421 s 0.004015 s 2.08 1.35 21 6min.
- pool : 2*2
- images : 256*256*32*32
0.096084 s 0.033422 s 0.01 s 9.60 3.34 18 8min.
- pool : 4*4
Les schedules générés par HalideAutotuner sont meilleurs que ceux produits à la main.
On remarque une accélération AccM qui est supérieure à 1.35 pour toutes les instances
de test. Le temps pris par HalideAutotuner pour optimiser l’algorithme MaxPool est
relativement petit, car l’algorithme est simple à optimiser (il ne contient qu’une seule
fonction consommatrice).
77
Chapitre VI. Tests et évaluations
et comparer entre son optimisation automatique faite par HalideAutotuner et son opti-
misation manuelle.
Nb.
Les entrées exe-Naïf exe-Manuel exe-Autotuner AccN AccM TS
schedules
- A : 32*400
0.031409 s 0.003519 s 0.003431 s 9.15 1.02 232 1h18min.
- B : 400 * 1024
- A : 8*400
0.02055 s 0.142774 s 0.013699 s 1.50 10.42 87 3h25min.
- B : 400*4096
- A : 1000*500
2.0878 s 0.023954 s 0.04688 s 44.53 0.51 507 3h57min.
- B : 500 * 2000
78
Chapitre VI. Tests et évaluations
L’algorithme de la multiplication matricielle par lots n’a pas été optimisé auparavant
par un expert, alors nous l’avons nous-mêmes optimisé. Pour ce faire, nous nous sommes
basés sur l’optimisation de la multiplication de matrice qui se trouve dans [Adams, 2016]
auquelle nous avons rajouté des optimisations de type parallélisation sur le niveau de
boucle externe de chaque fonction consommatrice (sur le niveau n), et nous avons exploré
plusieurs facteurs de tuilage et de découpage en bandes. On ne peut pas estimer le temps
qu’il nous a fallu pour l’optimisation manuelle car le schedule que nous avons proposé est
basé sur un autre qui existe déjà.
79
Chapitre VI. Tests et évaluations
Nb.
Les entrées exe-Naïf exe-Manuel exe-Autotuner AccN AccM TS
schedules
- matrice A : 512*100
- matrice B : 100*500 5.694 s 0.129501 s 0.115148 s 49.45 1.12 791 4h30min.
- lot : 100
- matrice A : 1000*500
- matrice B : 500*1000 34.814 s 1.021246 s 0.657695 s 52.93 1.55 819 8h06min.
- lot : 50
- matrice A : 256*500
- matrice B : 500*256 87.276 s 1.583942 s 0.869079 s 100.42 1.82 829 18h07min.
- lot : 1000
HalideAutotuner arrive à améliorer les algorithmes naïfs avec un facteur AccN su-
périeur à 49, et à construire des schedules qui sont non seulement compétitifs mais qui
améliorent encore le temps d’exécution de l’algorithme optimisé à la main (AccA supé-
rieur à 1.1). L’optimisation automatique des instances de test a été faite en prenant en
considération l’optimisation de fusion, pour améliorer la qualité des schedules. De ce fait,
HalideAutotuner explore un plus grand espace de recherche (un espace supérieur à 790
schedules) pour l’optimisation de la multiplication matricielle par lots.
80
Chapitre VI. Tests et évaluations
Par conséquent, nous nous sommes basés sur l’optimisation de la multiplication de ma-
trice transposée exposée dans [Adams, 2016], et nous avons rajouté une nouvelle dimension
à toutes les fonctions manipulées dans le programme (cette dimension est rajoutée pour
indexer les matrices multipliées, car on multiplie un lots de matrices et non pas deux ma-
trices). Nous avons rajouté à ce schedule des optimisations de parallélisation pour chaque
fonction consommatrice qui manipule la nouvelle dimension.
Nb.
Les entrées exe-Naïf exe-Manuel exe-Autotuner AccN AccM TS
schedules
- matrice A : 256*500
- matrice B : 500*256 173.34 s 1.076967 s 0.57198 s 303.05 1.88 281 8h16min.
- lot : 1000
- matrice A : 512*100
- matrice B : 100*500 5.9081 s 0.613993 s 0.120643 s 48.97 5.08 262 13h17min.
- lot : 100
- matrice A : 1000*500
- matrice B : 500*1000 83.893 s 4.21623 s 0.726127 s 115.53 5.80 245 13h53min.
- lot : 50
Le nombre de schedules explorés par HalideAutotuner est plus petit que celui exploré
pour l’optimisation de la multiplication de matrices par lots. Cela est dû à la désactiva-
tion de l’optimisation de fusion dans le cas de la multiplication matricielle transposée par
lots, ce qui a réduit l’espace à explorer. En effet, la prise en considération de l’optimi-
sation de fusion implique, un espace plus grand de 23 fois, comme il existe 3 fonctions
consommatrices dans le programme de la multiplication matricielle transposée par lots.
81
Chapitre VI. Tests et évaluations
HalideAutotuner générait parfois des schedules qui sont moins bons que ceux dévelop-
pés manuellement par des experts. Dans ces cas, nous avons repris les tests avec la prise
en considération de l’optimisation de fusion et nous avons remarqué que les schedules
se sont nettement optimisés. Cependant, pour les instances où les schedules générés par
HalideAutotuner était meilleurs que ceux développés manuellement, nous n’avons pas pris
en considération l’optimisation de fusion.
Suite aux analyses que nous avons faites sur les schedules construits par HalideAu-
totuner et ceux développés à la main, nous avons constatés plusieurs différences. Parfois
les optimisations sont appliquées sur la même fonction mais pas de la même façon : avec
des facteurs différents ou sur d’autres niveaux de boucles. Nous avons expliqué la diffé-
rence en termes de performances entre le schedule développé à la main et celui construit
par HalideAutotuner, par les facteurs de déroulage et de vectorisation utilisés, dans le
cas où ces deux optimisations sont appliquées sur la même fonction et avec des facteurs
différents. De même, on peut comparer entre les schedules suivant les facteurs de tuilage
choisis mais uniquement s’ils sont appliqués sur la même fonction et les mêmes niveaux
de boucles dans les deux schedules. Cependant, une optimisation peut ne pas apparaître
bénéfique pour un algorithme, mais elle peut l’être réellement une fois qu’elle est combinée
avec d’autres optimisations. De ce fait, on ne peut pas toujours expliquer la performance
d’un programme en fonction de ses optimisations.
VI.5 Conclusion
A travers ce chapitre, nous avons montrer les performances de la méthode HalideAu-
totuner sur des programmmes implémentant les réseaux de neurones convolutifs, et cela
pour différentes tailles d’entrée. Tous les résultats que nous avons présentés confirment
que HalideAutotuner est capable de générer des schedules compétitifs à ceux développés
à la main par des experts.
Les programmes optimisés à la main peuvent avoir une bonne performance, mais seule-
ment par rapport à un ensemble d’architectures qui partagent quelques caractéristiques
matérielles communes. Mais, lorsqu’un compilateur est utilisé pour tester les différentes
combinaisons d’optimisations, on peut approcher un schedule de bonne qualité sur n’im-
porte quelle architecture d’exécution.
82
Conclusion et perspectives
Notre projet de fin d’étude consiste à concevoir et développer une méthode pratique
qui optimise une classe de programmes Halide destinée à s’exécuter sur une architecture
CPU. Cette méthode est dite pratique, car elle doit optimiser chaque programme de la
classe en un temps inférieur à 24 heures. Le programme optimisé automatiquement doit
avoir un temps d’exécution qui approche celui du programme optimisé à la main (par des
experts en matière d’optimisation de programmes). La classe de programmes à laquelle
nous nous sommes intéressée est utilisée dans divers domaines applicatifs, et elle est ca-
ractérisée par un temps d’exécution élevé. C’est la classe des programmes implémentant
les réseaux de neurones convolutifs.
Nous avons insisté sur le fait qu’une optimisation n’est pas nécessairement bénéfique
pour un programme ; car d’une part, elle peut améliorer son temps d’exécution, et d’autre
part, elle peut le dégrader. L’effet d’une optimisation dépend de plusieurs paramètres dont
les caractéristiques matérielles de la machine qui exécute le programme, les optimisations
déjà appliquées sur le code et la structure de ce dernier. Tous ces facteurs ont rendu le
processus d’optimisation de programme complexe et non évident.
Afin d’alléger le travail des programmeurs, quelques compilateurs incluent des modules
pour l’optimisation automatique de leurs programmes. Afin d’optimiser les programmes,
les compilateurs se basent sur une ou une combinaison des quatre approches abordées
dans le second chapitre. Chaque approche a ses propres avantages et inconvénients en
termes de la qualité des optimisations renvoyées et le temps qu’elle prenne pour pouvoir
générer de bonnes optimisations. En effet, aucune approche n’est meilleure que les autres
dans l’absolu.
Dans le troisième chapitre, nous avons présenté le compilateur Halide et les diffé-
rentes méthodes d’optimisation automatique développées pour ce dernier. Halide étant
un langage dédié au domaine du traitement d’images, toutes les techniques d’optimisa-
tion automatique développées pour ce premier ont été testées sur des programmes de
traitement d’image.
Dans la quatrième partie, nous avons exposé la conception de notre système qui per-
met d’optimiser de façon automatique la classe des programmes Halide implémentant les
83
Chapitre VI. Tests et évaluations
Dans la cinquième partie, nous avons cité et justifié nos différents choix technologiques
pour le développement de notre système pour l’optimisation automatique des programmes
Halide.
En dernier, nous avons testé la performance de notre système sur sept benchmarks
qui permettent d’implémenter un réseau de neurones convolutifs. Nous avons montré que
notre méthode d’optimisation automatique permet de générer des programmes optimisés
qui sont compétitifs à ceux optimisés à la main par des experts du domaine dans un délai
inférieur à 24 heures.
Les résultats exposés dans la partie tests et évaluations montre la robustesse de notre
méthode d’optimisation automatique. En effet, nous sommes parvenus à construire des
combinaisons d’optimisations qui donnent des programmes optimisés qui sont non seule-
ment compétitifs à ceux optimisés à la main par les experts, mais dont le temps d’exécution
est bien meilleur dans 96% des cas testés.
Malgré les résultats satisfaisants que nous avons eu, mais notre méthode HalideAutotu-
ner présente quelques limites. Afin d’optimiser un programme, l’utilisateur doit introduire
un ensemble d’informations pour décrire la structure du programme dans un fichier d’an-
notation ‘source.settings’. Plus le programme à optimiser contient de fonctions Halide,
plus le nombre d’informations introduites par l’utilisateur est important ; au bout de cinq
fonctions Halide, la saisie du fichier ‘source.settings’ devient très lourde et pas pratique.
De plus, l’utilisateur doit prédire l’étendue de toute variable qui figure dans le programme,
afin que notre méthode puisse générer des combinaisons d’optimisations valides. Pour y re-
médier, nous proposons d’automatiser la création du fichier d’annotation ’source.settings’.
84
Chapitre VI. Tests et évaluations
Une fois que le modèle est entraîné, il sera utilisé dans notre système. En effet, pour de
nouveaux programmes, HalideAutotuner commence par décider de l’optimisation d’inter-
version de boucle en utilisant le modèle analytique de [Allen and Kennedy, 2002]. Ensuite,
il utilise la méta-heuristique du grimpeur pour décider de l’optimisation de granularité
de calcul. En dernier, il va utiliser le modèle entraîné ; ce dernier va avoir en entrée une
combinaison des facteurs de déroulage, vectorisation et tuilage et il est censé nous donner
une information sur est-ce que cette combinaison est bénéfique pour la fonction ou pas.
Nous allons tester plusieurs combinaisons de facteurs en entrée, et nous allons nous arrêter
à la première combinaison d’optimisations pour laquelle le modèle renvoie la valeur «vrai».
Cela n’était qu’une proposition pour réduire le temps d’exécution de la méthode Ha-
lideAutotuner. Dans le cas où le modèle proposé n’est pas précis (il n’a pas réussi à
prédire les bonnes optimisations pour une fonction), nous allons avoir des combinaisons
d’optimisations de qualité médiocre. En effet, le modèle doit être amélioré de façon itéra-
tive, en rajoutant d’autres caractéristiques ou en enlevant celles qui faussent ses décisions.
Le mode de la méthode d’optimisation automatique que nous avons proposé est offline,
ce qui veut dire que l’optimisation se fait en temps de compilation, où nous ne disposons
pas de toutes les informations (y compris la taille des entrées) sur notre programme à
optimiser. L’idéal serait de développer une méthode d’optimisation automatique online
qui dispose de toutes les informations nécessaires à l’exécution du programme et qui ne
prend pas beaucoup de temps pour s’exécuter. Pour ce faire, il faut développer un mo-
dèle ou une combinaison de modèles analytiques qui sont construits à partir d’une base
de données d’apprentissage en utilisant l’AA. Ces modèles seront capables de prédire les
bonnes optimisations pour un programme sans tester la performance d’aucune combinai-
son d’optimisations.
Pour conclure, ce projet de fin d’études nous a permis de combiner entre deux do-
maines informatiques attrayants qui sont l’optimisation de programmes et l’optimisation
combinatoire pour pouvoir optimiser de façon automatique la classe des programmes im-
plémentant les RNC dans le langage Halide.
85
Troisième partie
Annexes
Annexe A
Analyse de dépendance
L’analyse de dépendance est une théorie qui permet d’identifier toutes les contraintes
de l’ordre d’exécution entre chaque deux instructions du programme [Allen and Kennedy,
2002]. A titre d’exemple, si S1 et S2 sont deux instructions du programme, où S2 ap-
parait textuellement 1 après S1, la théorie d’analyse de dépendance sert à identifier les
contraintes de leur ordre d’exécution.
Figure 47: S2 dépend de S1 (S2 ne peut pas s’exécuter en parallèle avec S1)
87
A.1 Validité d’une optimisation
Notons que la théorie de l’analyse de dépendance est nécessaire pour décider de la
justesse et de la validité d’application d’une optimisation sur le programme [Bacon et al.,
1994]. En effet, une transformation sur le programme est valide, si elle maintient toutes les
contraintes d’ordre d’exécution qui apparaissent dans la version originale du programme.
– Les itérations d’une boucle imbriquée : Les itérations d’une boucle sont modélisées
par un ensemble { i1, i2, . . . , ik, . . . , in }, où ik[1 :n] est un vecteur qui contient les
valeurs de chaque index de boucle, organisées de la boucle la plus externe à la plus
interne, lors de l’itération k [Allen and Kennedy, 2002]. Soit la boucle de la figure
49, l’ensemble des itérations de cette boucle sont { 1, 2, 3,. . . , n }. Quant à la boucle
de la figure 50, l’ensemble de ses itérations sont : { (0,0), (0,1), (0,2), (1,0), (1,1),
(1,2), (2,0), (2,1), (2,2) }, où lors de l’itération i2 = (0,1), la boucle de i prend la
valeur 0 et la boucle de j prend la valeur 1.
2. A[i+j] est un champ du tableau A, on doit analyser les accès aux champs du tableau A à travers
l’expression i+j.
88
– La condition de dépendance dans les boucles : On dit que deux instructions S1 et S2
sont dépendantes s’il existe deux itérations i et j telles que i = j ou i précède j, où
S1 s’exécute à la i-ème itération et S2 s’exécute à la j-ème itération, et où S1 et S2
accèdent à la même localité mémoire M, avec au moins l’une d’entre elles qui tente
de modifier M [Allen and Kennedy, 2002].
– Les dépendances indépendantes des boucles : C’est une dépendance qui apparait
entre deux instructions S1 et S2 qui s’exécutent pendant la même itération (i=j),
autrement dit, d(i,j)= (0, 0,. . . ,0) [Allen and Kennedy, 2002].
– La dépendance à travers les boucles : C’est une dépendance entre deux instructions
S1 et S2, où S1 s’exécute pendant l’itération i et S2 pendant l’itération j, telles
que i<j. Autrement dit, ce type de dépendance a lieu s’il existe un entier non nul
dans le vecteur de distance d(i,j) [Allen and Kennedy, 2002]. L’exemple de la figure
50, illustre une dépendance à travers les boucles, car d(i,j) = (0,1), où le premier
élément différent de 0 est égal à 1. L’indice du premier élément différent de 0 dans
d(i,j) est appelé niveau de la dépendance à travers les boucles [Allen and Kennedy,
2002]. Dans l’exemple de la figure 50, il est égal à 2.
Une transformation sur le programme est dite valide si elle maintient les mêmes dé-
pendances que celles qui se trouvaient dans la version originale du programme. Autrement
dit, tous les vecteurs de distance positifs 3 dans la version originale du programme doivent
être maintenus dans la version transformée du code.
89
Annexe B
Figure 52: Code assembleur équivalent à celui de la boucle non déroulée de la figure 51
90
peuvent pas s’exécuter en parallèle malgré qu’elles soient indépendantes, car elles agissent
sur les mêmes registres, d’une itération à une autre. Par exemple, le registre R11 est des-
tiné à contenir B[i+1] et il ne peut pas contenir en même temps B[i+1] et B[i+2] pour la
prochaine itération.
Le déroulage se prête une bonne alternative pour assurer un parallélisme entre les
instructions, car elles vont agir sur des registres différents. Sur la figure 53, nous pouvons
charger simultanément les valeurs B[i+1], B[i+2] et B[i+3] car chacune d’elles s’appro-
priera un registre (dans le cas où la machine dispose d’assez de registres pour les contenir),
et nous pouvons dans ce cas-là charger toutes ces données dans leur endroit approprié qui
est A[i], A[i+1] et A[i+2].
La figure 55 illustre les défauts de cache et les succès cache lors de l’exécution du
programme de la figure 54 sans et avec tuilage (à facteurs 2*2). Nous nous sommes res-
treints à l’exécution des quatre premières itérations de la boucle imbriquée pour mieux
illustrer les choses dans la figure. Le prélecteur contient toutes les lignes de données qui
ont été préchargées de la mémoire principale vers la mémoire cache chronologiquement du
haut vers le bas, pour montrer les échanges entre ces deux mémoires. Quant à la mémoire
91
Figure 54: Tuilage avec facteurs : n*m (les étendues des deux boucles internes). A[i,j]
et B[j,i] sont des données multidimensionnelles. A[i,j] est accessible ligne par ligne,
mais B[j,i] est accessible colonne par colonne. Après le tuilage, A et B sont accessibles
en blocs de taille n*m.
cache, elle contient les données suite à toute l’exécution de la boucle imbriquée.
Figure 55: Nombre de défauts de cache pour la version tuilée de la boucle, et pour la
version sans tuilage.
Dans le cas où le tuilage n’est pas appliqué, le programme accède aux données A[1,1],
B[1,1], A[1,2], B[2,1], A[1,3], B[3,1], ensuite A[1,4] en dernier B[4,1]. Lors de l’accès à
A[1,1] toutes ses données adjacentes sont chargées avec elle (autant de données qu’une
ligne de cache peut supporter). De même, pour B[1,1]. Ensuite, la seconde itération accède
à A[1,2] et B[2,1]. A[1,2] est déjà dans le cache (succès cache) mais B[2,1] n y est pas donc
elle est chargée ainsi que ses données adjacentes (défaut de cache). L’accès à A[1,3] donne
un succès cache car la donnée y est toujours dans le cache, mais pour B[3,1] il faut la
charger car elle n y est pas dans le cache. Mais le cache est inondé, donc pour charger
92
B[3,1] il faut remplacer des données du cache par les données adjacentes à B[3,1]. Pour
cela, la ligne qui contient les données de A est supprimée et remplacée par B[3,1], B[3,2],
B[3,3] et B[3,4]. La prochaine itération fait référence à A[1,4] et B[4,1] qui produisent
toutes les deux un défaut de cache, car la première a été supprimée lors du remplacement
et la seconde n’a jamais été référencée. Pour l’exécution sans tuilage, on compte 6 défauts
de cache sur 8 accès mémoire.
Dans le cas où le tuilage est appliqué et avec un facteur de 2*2, le programme accède
aux données A[1,1], B[1,1], A[1,2], B[2,1], A[2,1], B[1,2], A[2,2], B[2,2] dans cet ordre. Lors
de l’accès à A[1,1] et B[1,1], toutes leurs données adjacentes sont chargées dans le cache.
Ensuite, les données A[1,2] et B[2,1], où A[1,2] se trouve déjà dans le cache (succès cache)
et B[2,1] n y est pas, donc elle doit être chargée. Lors de l’accès à A[2,1] et B[1,2], la
première produit un défaut de cache et ainsi elle et toutes ses données adjacentes sont
chargées en cache. Mais B[1,2] se trouve déjà dans le cache (succès cache). Enfin, l’accès à
A[2,2] et B[2,2] ne produit aucun défaut de cache, car les données ont été déjà préchargées.
93
Figure 56: Implémentation naïve du programme à 3 tableaux : input, Blurx et Blury
Cette implémentation est connue sous le nom de : fusion totale, car elle minimise au
maximum la distance entre l’instant où une donnée est produite et l’instant où elle est
consommée. En effet, cette implémentation utilise de façon efficace la mémoire cache, car
une fois la donnée produite, elle est rapidement consommée, et on a plus de probabilité de
la retrouver dans le cache. De plus, cette implémentation garde le même degré de parallé-
lisme que celui de la version naïve. Néanmoins, un recalcul de données est imposé, car pour
pouvoir paralléliser le calcul de Blury, on doit recalculer des valeurs de Blurx. Par exemple,
pour calculer Blury(x,y), on a besoin de Blurx(x,y), Blurx(x,y+1) et Blurx(x,y-1), et pour
calculer Blury(x,y+1) on aura besoin de Blurx(x,y) et Blurx(x,y+1) et Blurx(x,y+2). Par
conséquent, pour pouvoir paralléliser le calcul de Blury(x,y) et Blury(x,y+1) on a besoin
de calculer les valeurs de Blurx(x,y) et Blurx(x,y+1) deux fois.
94
toutes les valeurs du tableau Blurx[m,3] correspondant (voir figure 59).
Figure 59: Schéma démontrant le flux de calcul des données dans l’im-
plémentation de la fenêtre coulissante sur le programme à trois tableaux.
A travers les implémentations que nous avons expliquées en haut, nous avons pu mettre
l’accent sur trois concepts et facteurs qui sont en tension, et qui ne peuvent coexister dans
la même implémentation : le parallélisme, la localité des données et le recalcul des données.
95
Annexe C
Le framework OpenTuner
Toutes les techniques sont coordonnées par une super technique appelée méta-technique,
qui est responsable de l’allocation du nombre de tests pour chaque technique d’exploration.
Cette méta-technique favorise la technique d’exploration qui éprouve des améliorations
(trouve des combinaisons de plus en plus performantes) en lui affectant plus de configura-
tions à tester, et néglige les techniques qui donnent des résultats médiocres, et peut même
arriver jusqu’à les désactiver (voir figure 60). La combinaison des techniques d’explora-
tion utilisées par défaut dans OpenTuner regroupe : l’évolution différentielle, la mutation
avare et deux variantes de l’escaladeur (Hill climber), orchestrées par une méta-technique.
Les paramètres d’OpenTuner sont inspirés des types de paramètres à autorégler ren-
contrés dans les cas pratiques et les projets d’Autotuning. Nous retrouvons des paramètres
dits primitifs comme : les entiers, les puissances de deux, qui sont des paramètres variables
ayant une borne minimale et une borne maximale, où toute configuration de l’espace
de recherche prend une valeur entre ces bornes. Tandis que, les paramètres complexes,
comme : le paramètre BooleanParameter, qui prend soit la valeur 0 ou 1. Le paramètre
96
Figure 60: Architecture globale d’OpenTuner. α, β, γ présentent des coefficients affectés de la
méta-heuristique du bandit multi-armé aux différentes techniques de recherche, où celle qui dispose
du plus grand coefficient sera exécutée plus que les autres.
EnumParameter pour le choix d’un élément parmi une liste d’énumérations. Le paramètre
ScheduleParameter, qui consiste à organiser les éléments d’une liste avec des contraintes
de précédence.
97
Figure 62: Exemple de déclaration d’un paramètre Halide à autorégler
98
Annexe D
Figure 63: Le corps de la fonction Blurx en Halide, extrait du corps de la fonction de troublement
d’une image.
Pour décider par exemple, de l’ordre de parcours de la fonction Blurx, on doit calculer
le taux de réutilisation garanti par chaque parcours : ligne par ligne (voir figure 64) et
colonne par colonne (voir figure 65) pour cette fonction.
Figure 64: Code équivalent du parcours ligne par ligne de la fonction Blurx.
Si le parcours de la fonction Blurx est de ligne par ligne, alors x représente la dimension
de la boucle la plus interne (voir 64) :
99
– Le calcul d’une valeur de Blurx, réutilise deux valeurs d’input qui ont été déjà
calculées dans l’itération précédente : input(x-1,y) et input(x,y).
– Le taux de réutilisation = 2.
Figure 65: Code équivalent du parcours colonne par colonne de la fonction Blurx.
Figure 66: Rectangle minimal qui englobe les valeurs de la fonction productrice f nécessaires au
calcul d’une valeur de la fonction consommatrice g.
100
– L’AutoTuner substitue l’appel à la fonction productrice, par son expression arith-
métique (son corps).
101
Annexe E
Choix de conception
Chaque type d’optimisation dispose de ses propres attributs qui dépendent de la struc-
ture de l’optimisation concernée. Par exemple, l’optimisation SplitOptim a comme attri-
buts : split_facteur qui représente le facteur de découpage en bandes et split_var la
variable sur laquelle on applique le découpage en bandes. Toutes les optimisations, indé-
pendemment de leur type, sont appliquées sur une Fonction, qui est caractérisée par un
nom, une liste de type Variable pour retenir les variables manipulées par la fonction, et
une liste de type Fonction pour contenir ses fonctions productrices. Toutes les fonctions
sont regroupées dans l’entité Programme, qui rassemble d’autres informations comme le
nom du programme, le chemin vers le code source du programme Halide, les constantes
du programme ...etc.
Chaque fois qu’un schedule est construit, il est testé en utilisant la classe Execu-
tionSchedule qui prend le soin de transformer l’objet schedule en un code source, de le
fusionner avec le code source du programme en entrée, et de les mettre dans un même
fichier source. La méthode execute_source lance la compilation du code source puis son
exécution pour récupérer le temps d’exécution du programme optimisé.
102
Figure 67: Diagramme de classe pour la recherche exhaustive.
rogation de la base de données. Par exemple, avant de commencer à générer les sche-
103
dules, l’entité Programme est sauvegardée dans la base données par la méthode sauvegar-
der_programme et chaque schedule testé est sauvegardé ainsi que son temps d’exécution
par le biais de la méthode sauvegarder_schedule.
Par exemple, la classe SplitFactorRes qui est une restriction de type SplitRestriction
permet de déterminer la plage de valeurs que peut prendre le facteur de découpage d’une
optimisation de type SplitOptimisation et la classe FixParallelRes permet de maintenir
ou d’éliminer l’optimisation de parallélisation pour une fonction lors de la construction
des schedules.
104
Figure 68: Diagramme de classe pour la description des entités du système.
105
tités de type Restriction. De part cette conception, le développeur peut facilement étendre
le système en définissant de nouvelles restrictions et en leur implémentant la méthode res-
trict pour définir la nouvelle stratégie d’exploration d’espace. Pareil, le développeur peut
développer de nouvelles méthodes d’exploration dans notre système en faisant uniquement
changer les restrictions définies sur l’espace de recherche.
106
Annexe F
Ce modèle vise à trouver une bonne interversion de boucle pour toute boucle parfai-
tement imbriquée. Il consiste à pousser vers l’intérieur d’une boucle imbriquée le niveau
de boucle qui comporte le plus de réutilisation de données, pour profiter de l’accès rapide
aux données tant qu’elles sont dans le cache (principe de la localité temporelle 1 ). De
plus, elle vise aussi à rendre l’accès séquentiel aux différentes données manipulées dans
la boucle (principe de la localité spatiale 2 ). Par conséquent, le modèle analytique qu’il
propose pour le choix automatique d’une bonne optimisation d’interversion de boucle se
base sur l’estimation du nombre de défauts de cache engendrés lors du positionnement
d’un niveau de boucle comme le niveau le plus interne à une boucle imbriquée. Ce modèle
prend en entrée la boucle à optimiser et la taille d’une ligne de cache pour pouvoir estimer
le nombre de défauts de cache pour les interversions de boucle candidates.
1. une donnée qui a été déjà référencée est susceptible d’être référencée encore une fois.
2. une donnée est susceptible d’être référencée si une donnée adjacente à elle vient d’être référencée
3. A[1,1] ensuite A[2,1], ..., A[N,1], A[2,1], A[2,2], ..., A[N, 2]...
4. celles susceptibles qu’elles soient référencées dans le futur proche
107
Figure 70: Boucle imbriquée de profondeur 2 d’étendues égales à N*M, qui manipule les deux
tableaux A et B (accès colonne par colonne) [Allen and Kennedy, 2002].
Figure 71: Défauts de cache engendrés lors des premiers accès au tableau A 3*4.
Si on intervertit les deux niveaux de boucle de la figure 72, l’accès aux éléments du
tableau A et à ceux du tableau B devient séquentiel (suivant leur ordre de stockage en
mémoire centrale), et on aura un nombre de défauts de cache égal à 2*N*M/b, tel que b
représente la taille d’une ligne de cache. En effet, le prélecteur charge autant de données
adjacentes à la donnée référencée à t=t0 que la taille d’une ligne de cache, alors, un défaut
de cache ne se produit qu’une fois on accède à une donnée qui n’a pas été chargée à t=t0
dans la ligne de cache.
Figure 72: Boucle imbriquée de profondeur 2 d’étendues égales à N*M, qui manipule les deux
tableaux A et B (accès ligne par ligne).
108
Algorithme 12 Pseudo-algorithme du modèle analytique de [Allen and Kennedy, 2002]
pour le choix automatique de l’optimisation d’interversion de boucle pour une boucle
imbriquée
1: Soit Bi un niveau de boucle de la boucle B.
2: Soit C(Bi) une estimation du nombre de défauts de cache lors du positionnement de
la boucle Bi comme le niveau de boucle le plus interne à B.
3: Soit Tx un tableau parmi les tableaux T de B.
4: Soit b la taille d’une ligne de cache (le nombre de données que peut contenir une ligne
de cache) ;
5: Soit c(Bi, Tx) une estimation du nombre de défauts de cache produit lors de l’appel
de Tx au niveau de la boucle Bi
6: for Niveau de boucle Bi dans B do
7: C(Bi) = 0
8: for Tableau T x dans T do
9: if Tx ne dépend pas de l’indice de boucle de Bi, c-à-d la réutilisation est assurée
then
10: c(Bi, T x) = 1
11: if Tx dépend de l’indice de boucle de Bi, et Tx est accessible sur un espace
d’adresse non contigu, c-à-d qu’à chaque accès à Tx provoque un défaut de cache.
then
12: c(Bi, T x) = N i
13: if Tx dépend de l’indice de boucle de Bi, et Tx est accessible sur un espace
d’adresse contigu, c-à-d après chaque b données de Tx il y a un défaut de cache, sinon
on est sur une réutilisation de données. then
14: c(Bi, T x) = N i/b
15: for Niveau de boucle Bj dans B do
16: if Bj 6= Bi then
17: if Tx ne varie pas avec l’indice de boucle de Bj then
18: c(Bi, T x) = c(Bi, T x) ∗ 1
19: if Tx varie avec l’indice de boucle de Bj then
20: c(Bi, T x) = c(Bi, T x) ∗ N j
21: C(Bi) = C(Bi) + c(Bi, T x)
22: B’ = Ordonner les boucles B dans l’ordre croissant de leur nombre de défauts de cache
estimé C(Bi).
23: Retourner B’
109
Annexe G
Le but d’un réseau de neurones est d’essayer d’approcher la valeur f(x) pour une valeur
x en entrée. Autrement dit, pour une valeur de x injectée au niveau des neurones d’entrée,
on s’apprête à avoir en sortie (au niveau du neurone de sortie) une valeur f* qui est proche
de f(x). Par exemple, si nous cherchons à détecter la présence d’un chien dans une image,
x = {l’ensemble des valeurs de pixels de l’image}, et f(x) = {0 si l’image ne renferme
pas de chien, 1 sinon}. Dans ce cas, chaque neurone d’entrée va contenir la valeur d’un
seul pixel de l’image, et après avoir terminé tous les calculs et transité à travers toutes
les couches intermédiaires, le neurone de la couche de sortie est censé renvoyer 0 ou 1. Le
réseau de neurones rassemble un certain nombre de paramètres dynamiques D, qui sont
ajustés au fur et à mesure du déroulage de l’algorithme : ils sont ajustés de telle façon à
réduire l’écart entre f(x) qui est la valeur effective et f*(x, D) qui est la valeur prédite en
110
fonction des paramètres dynamiques. Pour pouvoir comprendre comment cela peut-il se
faire, nous allons aborder : la structure du neurone et les différentes couches du réseau de
neurones.
Les poids : Les poids représentent des nombres réels qui servent à pondérer la valeur
retournée par un neurone de la couche précédente : plus le poids est grand, plus la valeur
retournée par le neurone de la couche j-1 est significative lors du calcul de la valeur S.
Les valeurs des poids associés à un neurone, font partie des paramètres dynamiques du
réseau de neurones, et donc elles ne sont pas fixées au préalable, mais elles sont construites
et apprises au fur et à mesure de l’apprentissage : à chaque fois qu’une valeur de x est
introduite dans le réseau de neurones, les valeurs des poids subissent des changements,
pour que la valeur en sortie f* du réseau de neurones puisse atteindre ou approcher au
maximum la valeur effective f(x). Par ailleurs, les poids sont choisis de telle façon à réduire
la fonction perte, qui représente la distance entre la valeur de sortie effective f(x) et la
valeur de sortie estimée f*.
La fonction d’activation : Les problèmes du monde réel ne peuvent pas être tous
modélisés par une fonction linéaire, i.e. la sortie n’est pas toujours représentée par une
111
combinaison linéaire des valeurs en entrée. En effet, la fonction d’activation est appli-
quée sur la combinaison calculée au niveau de chaque neurone du réseau, pour ne pas
restreindre le réseau de neurones à l’apprentissage de modèles linéaires uniquement. Il
existe plusieurs fonctions d’activation standards qui sont utilisées en pratique : la fonc-
tion ReLU, la fonction Sigmoid, la fonction Tangente hyperbolique . . . etc. Par exemple,
la fonction ReLU(x) = max {0, x}, pour fonction d’activation linéaire rectifiée, est une
fonction très utilisée dans les réseaux de neurones, qui consiste à casser la linéarité, pas
de façon radicale, entre l’entrée et la sortie.
Le biais : Le biais ou le seuil est une valeur qui sert généralement à activer ou
désactiver un neurone : Dans le cas où nous voulons désactiver un neurone parce qu’il
revoit une valeur inférieure à un seuil θ, on rajoute à la combinaison linéaire calculée au
niveau de N, S = φ (x1*w1j+ x2*w2j+ x3*w3j+ . . . + xn*wnj + θ), la valeur θ. Ainsi,
lors de l’application de la fonction d’activation, ReLU par exemple, elle va renvoyer la
valeur 0 pour toute combinaison dont la valeur est inférieure au seuil θ, car ReLU(x) =
max{0, x}.
Comme son nom l’indique, un réseau de neurones convolutif emploie une opération
connue sous le nom de : convolution, cette technique consiste à détecter des patrons à
partir d’une donnée en entrée. Les réseaux de neurones convolutifs s’apprêtent bien pour
des données en entrée organisées en grille, à titre d’exemple, les images qui sont des don-
nées à base de grille à deux dimensions.
La figure 75 illustre un exemple d’un réseau de neurone convolutif. Les neurones en en-
trée du réseau prennent comme valeur les pixels de l’image en entrée. La première couche
du réseau est celle de la convolution. C’est une couche caractérisée par un ensemble de k
filtres, où chacun extrait une caractéristique de l’image en entrée. Suite à cette couche de
convolution, on se retrouve avec k cartes de caractéristiques ; chaque carte i est le résultat
de l’application du filtre i sur l’image de la couche précédente.
La couche qui succède celle de la convolution est la couche de maxpool qui ne garde
que la valeur maximale parmis toutes les valeurs d’une proportion de taille n*m extraite
à partir de chaque carte de caractéristiques. Effectivement, la couche maxpool permet de
112
Figure 75: Exemple d’un réseau de neurones convolutifs.
réduire le nombre d’informations véhiculées par la couche précédente 1 . Maxpool est utile
pour la réduction de la dimension du réseau de neurone.
1. Surtout que la couche de convolution fait augmenter les cartes de caractéristiques et donc les données
à traiter par le réseau.
113
Bibliographie
[Agakov et al., 2006] Agakov, F., Bonilla, E., Cavazos, J., Franke, B., Fursin, G., OBoyle,
M., Thomson, J., Toussaint, M., and Williams, C. K. (2006). Using machine learning
to focus iterative optimization. In Proceedings of the International Symposium on Code
Generation and Optimization, pages 295–305. IEEE Computer Society.
[Allen and Kennedy, 2002] Allen, R. and Kennedy, K. (2002). Optimizing compilers for
modern architectures : a dependence-based approach, volume 1. Morgan Kaufmann San
Francisco.
[Ansel et al., 2014] Ansel, J., Kamil, S., Veeramachaneni, K., Ragan-Kelley, J., Bosboom,
J., O’Reilly, U.-M., and Amarasinghe, S. (2014). Opentuner : An extensible framework
for program autotuning. In Parallel Architecture and Compilation Techniques (PACT),
2014 23rd International Conference on, pages 303–315. IEEE.
[Ansel, 2014] Ansel, J. J. A. (2014). Autotuning programs with algorithmic choice. PhD
thesis, Massachusetts Institute of Technology.
[Ashouri et al., 2016] Ashouri, A. H., Mariani, G., Palermo, G., Park, E., Cavazos, J., and
Silvano, C. (2016). Cobayn : Compiler autotuning framework using bayesian networks.
ACM Transactions on Architecture and Code Optimization (TACO), 13(2) :21.
[Bacon et al., 1994] Bacon, D. F., Graham, S. L., and Sharp, O. J. (1994). Compiler
transformations for high-performance computing. ACM Computing Surveys (CSUR),
26(4) :345–420.
[Bergstra et al., 2012] Bergstra, J., Pinto, N., and Cox, D. (2012). Machine learning for
predictive auto-tuning with boosted regression trees. In Innovative Parallel Computing
(InPar), 2012, pages 1–9. IEEE.
[De Mesmay et al., 2010] De Mesmay, F., Voronenko, Y., and Puschel, M. (2010). Offline
library adaptation using automatically generated heuristics. In Parallel & Distributed
Processing (IPDPS), 2010 IEEE International Symposium on, pages 1–10. IEEE.
114
[Epshteyn et al., 2005] Epshteyn, A., Garzaran, M. J., DeJong, G., Padua, D., Ren, G.,
Li, X., Yotov, K., and Pingali, K. (2005). Analytic models and empirical search : A
hybrid approach to code optimization. In International Workshop on Languages and
Compilers for Parallel Computing, pages 259–273. Springer.
[Mullapudi et al., 2016] Mullapudi, R. T., Adams, A., Sharlet, D., Ragan-Kelley, J., and
Fatahalian, K. (2016). Automatically scheduling halide image processing pipelines.
ACM Transactions on Graphics (TOG), 35(4) :83.
[Park et al., 2011] Park, E., Kulkarni, S., and Cavazos, J. (2011). An evaluation of dif-
ferent modeling techniques for iterative compilation. In Proceedings of the 14th inter-
national conference on Compilers, architectures and synthesis for embedded systems,
pages 65–74. ACM.
[Ragan-Kelley et al., 2012] Ragan-Kelley, J., Adams, A., Paris, S., Levoy, M., Amara-
singhe, S., and Durand, F. (2012). Decoupling algorithms from schedules for easy
optimization of image processing pipelines.
[Ragan-Kelley et al., 2013] Ragan-Kelley, J., Barnes, C., Adams, A., Paris, S., Durand,
F., and Amarasinghe, S. (2013). Halide : a language and compiler for optimizing pa-
rallelism, locality, and recomputation in image processing pipelines. ACM SIGPLAN
Notices, 48(6) :519–530.
[Rahman et al., 2010] Rahman, M., Pouchet, L.-N., and Sadayappan, P. (2010). Neural
network assisted tile size selection. In International Workshop on Automatic Perfor-
mance Tuning (IWAPT’2010). Berkeley, CA : Springer Verlag.
[Stephenson et al., 2003] Stephenson, M., Amarasinghe, S., Martin, M., and O’Reilly, U.-
M. (2003). Meta optimization : improving compiler heuristics with machine learning.
In ACM SIGPLAN Notices, volume 38, pages 77–90. ACM.
115
[Tiwari et al., 2009] Tiwari, A., Chen, C., Chame, J., Hall, M., and Hollingsworth, J. K.
(2009). A scalable auto-tuning framework for compiler optimization. In Parallel &
Distributed Processing, 2009. IPDPS 2009. IEEE International Symposium on, pages
1–12. IEEE.
[Yotov et al., 2003] Yotov, K., Li, X., Ren, G., Cibulskis, M., DeJong, G., Garzaran, M.,
Padua, D., Pingali, K., Stodghill, P., and Wu, P. (2003). A comparison of empirical
and model-driven optimization. ACM SIGPLAN Notices, 38(5) :63–76.
116