Spaces:
Running
Running
<html> | |
<head> | |
<script src="distill.bundle.js" type="module" fetchpriority="high" blocking></script> | |
<script src="main.bundle.js" type="module" fetchpriority="low" defer></script> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<meta charset="utf8"> | |
<base target="_blank"> | |
<title>Le FAT5 : Flash Attention T5</title> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<d-front-matter> | |
<script id='distill-front-matter' type="text/json">{ | |
"title": "Le FAT5 : Flash Attention T5", | |
"description": "", | |
"published": "May 28, 2024", | |
"authors": [ | |
{ | |
"author":"Boris ALBAR", | |
"authorURL":"https://github.com/b-albar", | |
"affiliation": [{"name": "CATIE", "url": "https://catie.fr"}] | |
}, | |
{ | |
"author":"Loïck BOURDOIS", | |
"authorURL":"https://github.com/lbourdois", | |
"affiliation": [{"name": "CATIE", "url": "https://catie.fr"}] | |
} | |
], | |
"color": "#9CA3AF", | |
"katex": { | |
"delimiters": [ | |
{"left": "$$", "right": "$$", "display": false} | |
] | |
} | |
} | |
</script> | |
</d-front-matter> | |
<d-title> | |
<h1 class="l-page" style="text-align: center;">Le FAT5 : Flash Attention T5</h1> | |
<p><img src="./assets/FAT5_dark.gif" alt="FAT5" width="100%"></p> | |
</d-title> | |
<d-article> | |
<d-contents> | |
</d-contents> | |
<div class="note">Pour une meilleure expérience, nous déconseillons la lecture sur téléphone portable</div> | |
<br> | |
<h2 id="motivation">Motivation</h2> | |
<p class="width_125"> | |
Alors que beaucoup d’efforts ont été consacrés à l’optimisation de <i>transformer</i> de type décodeur, abandonnant ainsi l’encodeur, | |
nous pensons qu’il est essentiel de maintenir une architecture encodeur-décodeur.<br> | |
En effet, cette architecture qui présente des performances intéressantes pour l’instruction tuning <d-cite bibtex-key="chia2023instructeval"></d-cite>, | |
est propice à la distillation <d-cite bibtex-key="hsieh2023distilling"></d-cite> et semble supérieure aux modèles décodeur lorsqu’elle est finetunée <d-cite bibtex-key="fu2024tiny"></d-cite>. | |
Il a aussi été montré que les modèles encodeur-décodeur entraînés avec une modélisation du langage masqué obtiennent une meilleure performance zéro-shot | |
après un finetuning multitâche par rapport à un modèle décodeur <d-cite bibtex-key="wang2022languagemodelarchitecturepretraining"></d-cite>.<br> | |
Au-delà du NLP sur lequel nous nous concentrons dans cet article de blog, l’architecture encodeur-décodeur est très utilisée dans d’autres domaines | |
comme l’audio ou les séries temporelles par exemple. L'encodeur d'une telle architecture est également utilisé dans des modèles de diffusion.<br> | |
Dans cette logique, nous avons décidé de nous concentrer sur le T5 <d-cite bibtex-key="JMLR:v21:20-074"></d-cite>.<br><br> | |
Dans cet article, sont détaillons les optimisations que nous avons mises en place afin de pré-entraîner de manière efficiente un T5 de 147M de paramètres en français en un temps raisonnable (1 461 H pour 419Mds de <i>tokens</i>) et avec des moyens limités (1 unique A100 ; soit un budget de calcul d'environ 2 200 euros). | |
Pour ce faire, nous avons conçu des noyaux CUDA/Triton afin de rendre la Flash Attention compatible avec T5 et de fournir une inférence linéaire, étendant ainsi la taille du contexte qui peut être prise en compte par le modèle.<br><br> | |
<strong>Le code de pré-entrainement est disponible sur notre répertoire <a class="link" href="https://github.com/catie-aq/flashT5">GitHub</a> sous licence Apache-2.0. et les poids sur notre compte <a class="link" href="https://hf.co/CATIE-AQ">Hugging Face</a>.</strong> | |
<p class="width_125"><br><br><br></p> | |
<h2 id="vue-d-ensemble-de-notre-travail">Vue d’ensemble de notre travail</h2> | |
<p class="width_125">Nous avons donc choisi de travailler avec un T5 et en pratique avec le nanoT5 <d-cite bibtex-key="nawrot2023nanot5"></d-cite>.<br> | |
Pour les tâches de prétexte lors du pré-entraînement, nous avons suivi celles d'UL2 <d-cite bibtex-key="tay2023ul2"></d-cite> avec les 7 tâches suivantes :</p> | |
<pre><code class="lang-py"> | |
denoiser_list=[ | |
{<span class="hljs-string">"mu"</span>: <span class="hljs-number">3.0</span>, <span class="hljs-string">"r"</span>: <span class="hljs-number">0</span>.<span class="hljs-number">15</span>, <span class="hljs-string">"max_spans"</span>: max_token_length, <span class="hljs-string">"prefix"</span>: <span class="hljs-string">"[R]"</span>}, | |
{<span class="hljs-string">"mu"</span>: <span class="hljs-number">8.0</span>, <span class="hljs-string">"r"</span>: <span class="hljs-number">0</span>.<span class="hljs-number">15</span>, <span class="hljs-string">"max_spans"</span>: max_token_length, <span class="hljs-string">"prefix"</span>: <span class="hljs-string">"[R]"</span>}, | |
{<span class="hljs-string">"mu"</span>: <span class="hljs-number">4.0</span>, <span class="hljs-string">"r"</span>: <span class="hljs-number">0</span>.<span class="hljs-number">0</span>, <span class="hljs-string">"max_spans"</span>: <span class="hljs-number">1</span>, <span class="hljs-string">"prefix"</span>: <span class="hljs-string">"[S]"</span>}, | |
{<span class="hljs-string">"mu"</span>: <span class="hljs-number">3.0</span>, <span class="hljs-string">"r"</span>: <span class="hljs-number">0</span>.<span class="hljs-number">5</span>, <span class="hljs-string">"max_spans"</span>: max_token_length, <span class="hljs-string">"prefix"</span>: <span class="hljs-string">"[X]"</span>}, | |
{<span class="hljs-string">"mu"</span>: <span class="hljs-number">8.0</span>, <span class="hljs-string">"r"</span>: <span class="hljs-number">0</span>.<span class="hljs-number">15</span>, <span class="hljs-string">"max_spans"</span>: max_token_length, <span class="hljs-string">"prefix"</span>: <span class="hljs-string">"[X]"</span>}, | |
{<span class="hljs-string">"mu"</span>: <span class="hljs-number">64.0</span>, <span class="hljs-string">"r"</span>: <span class="hljs-number">0</span>.<span class="hljs-number">15</span>, <span class="hljs-string">"max_spans"</span>: max_token_length, <span class="hljs-string">"prefix"</span>: <span class="hljs-string">"[X]"</span>}, | |
{<span class="hljs-string">"mu"</span>: <span class="hljs-number">64.0</span>, <span class="hljs-string">"r"</span>: <span class="hljs-number">0</span>.<span class="hljs-number">5</span>, <span class="hljs-string">"max_spans"</span>: max_token_length, <span class="hljs-string">"prefix"</span>: <span class="hljs-string">"[X]"</span>}] | |
denoiser_proportions=[<span class="hljs-number">0</span>.<span class="hljs-number">165</span>, <span class="hljs-number">0</span>.<span class="hljs-number">165</span>, <span class="hljs-number">0</span>.<span class="hljs-number">34</span>, <span class="hljs-number">0</span>.0825, <span class="hljs-number">0</span>.0825, <span class="hljs-number">0</span>.0825, <span class="hljs-number">0</span>.0825] | |
</code></pre> | |
<p class="width_125">avec <code>mu</code> la taille du n-gram, <code>r</code> le pourcentage de masquage dans le n-gram et <code>prefix</code> le type de tâche de prétexte. | |
La signification des lettres <code>[R]</code>, <code>[S]</code> et <code>[X]</code> est décrite <a class="link" href="https://huggingface.co/google/ul2#mixture-of-denoisers">ici</a> | |
et nous vous invitons à consulter notamment l'image explicative <a class="link" href="https://raw.githubusercontent.com/google-research/google-research/master/ul2/figs/mod.png">ici</a>.</p> | |
<p class="width_125">Pour avoir un entraînement rapide, nous nous sommes orientés sur la Flash Attention <d-cite bibtex-key="dao2022flashattention"></d-cite>. | |
Cependant, comme celle-ci ne gère pas les biais attentionnels (additifs) du T5, nous avons dû l’étendre en développant un noyau personnalisé. | |
Plus précisément nous avons développé successivement deux versions de ce noyau. | |
Dans la première version, au début de notre travail nous transmettions la matrice de biais au noyau. | |
Dans la version actuelle, nous inspirant du TurboT5 <d-cite bibtex-key="turbot5"></d-cite>, | |
nous communiquons seulement un tenseur contenant les biais fusionnés afin de matérialiser la matrice de biais à la volée. | |
Cela permet de passer d’un T5 à mémoire quadratique à un T5 à mémoire linéaire, et par conséquent augmente fortement la taille de contexte que le modèle peut gérer.</p> | |
<p class="width_125">Notre travail a abouti au pré-entraînement d'un T5 en français de 147M de paramètres : le FAT5 <i>small</i>.<br> | |
Le jeu de données que nous avons utilisé est composé de la partie en français du corpus CulturaX <d-cite bibtex-key="nguyen2023culturax"></d-cite> | |
(principale source avec plus de 1258 Go de texte), | |
de la partie en français de Wikipedia <d-cite bibtex-key="wikidump"></d-cite> (dump 20231101), | |
de justice_fr (textes de loi française) <d-cite bibtex-key="justice_fr"></d-cite>, | |
et de 25 000 000 lignes de TheStack <d-cite bibtex-key="Kocetkov2022TheStack"></d-cite> | |
(l'idée ici est de montrer un peu de code à notre modèle bien que ce ne soit pas notre objectif principal).<br> | |
Ce modèle a été évalué sur cinq tâches : le résumé de textes, la classification binaire, le question answering, la reconnaissance d’entités nommées et la <i>sentence similarity</i>.</p> | |
<p class="width_125"><br><br><br></p> | |
<h2 id="les-d-tails-de-la-recette">Les détails de la recette</h2> | |
<p class="width_125">Ne disposant que de deux A100 (une de 80Go et une de 40Go), nous avons dû consacrer un peu de temps afin d’implémenter des optimisations permettant d'exploiter au mieux notre matériel. | |
En effet, avant même d’entraîner un modèle voire de modifier son architecture, il faut s’assurer qu’on optimise l'usage des capacités de calcul de nos GPU. | |
Plusieurs facteurs peuvent expliquer un entraînement sous-optimal d’un modèle de deep learning :<br> | |
• Le manque de nourriture (<em>disk-bounded</em> - limitation en disque)<br> | |
• La bande passante mémoire (<em>memory-bounded</em> - limitation en bande passante mémoire)<br> | |
• La vitesse de calcul (<em>compute-bounded</em> - limitation en calcul)</p> | |
<p class="width_125">Idéalement, on aimerait que le modèle soit limité par la vitesse de calcul, c’est-à-dire que le GPU soit utilisé à pleine capacité. | |
Partant de ce constat, nous avons travaillé sur trois points principaux : <br> | |
• L’optimisation du disque du GPU <br> | |
• L’optimisation de la bande passante de la mémoire du GPU <br> | |
• L’optimisation de l’utilisation des Tensor Cores<br> | |
</p> | |
<p class="width_125">Il s’agit donc de points à la fois hardware mais aussi software.</p> | |
<p></p> | |
<p class="width_125">Dans la suite de cette section, tout ce que nous avons réalisé/implémenté pour apporter une réponse aux limites rencontrées est disponible dans un encadré en vert. Des notes/commentaires sont trouvables dans un encadré en bleu. | |
<br><br></p> | |
<h3 id="optimisation-du-disque-du-gpu">Optimisation du disque du GPU</h3> | |
<p class="width_125">La limitation en disque intervient soit lors du chargement des données, soit lors des opérations de prétraitement. | |
Dans les deux cas, cela se matérialise par un problème de lenteur. | |
<br></p> | |
<h4 id="acc-s-disques">Accès disques</h4> | |
<p class="width_125">Si la limitation vient des accès disques, plusieurs solutions sont possibles :</p> | |
<ul> | |
<li><p class="width_125"><u>Mettre les données en RAM</u><br> | |
Cela résout le problème de manière radicale mais suppose que la base de données rentre en RAM, ce qui est loin d’être évident du fait de sa petite taille.</p> | |
<div class="tip"><p>Ce n’est donc pas la solution que nous avons retenue.</p></div> | |
</li> | |
<li><p class="width_125"><u>Mettre les données sur un disque plus rapide et/ou moins utilisé</u><br> | |
Si vous avez un accès physique à votre serveur de GPU, il est très utile d’intégrer des <a class="link" href="https://fr.wikipedia.org/wiki/NVM_Express">NVMe</a> dans la configuration de celui-ci.</p> | |
<p class="width_125">Il faut aussi faire attention à ne pas avoir trop de processus de différents entraînements qui tirent sur un même disque. | |
Il est alors préférable d’avoir plusieurs petits disques plutôt qu’un gros. </p> | |
<div class="note"><p>Un effet indirect bénéfique est qu’une telle configuration coûte moins cher 😉</p></div> | |
</li> | |
</ul> | |
<ul> | |
<li class="width_125"><u>Utiliser des formats de fichiers plus efficients notamment au niveau des accès aléatoires</u><br> | |
Par exemple les fichiers <code>.parquet</code> sont plus efficients que les <code>.csv</code>. | |
On peut aussi utiliser des formats spécifiquement développés dans ce but comme le <code>.beton</code> de ffcv <d-cite bibtex-key="leclerc2023ffcv"></d-cite>.</li> | |
<div class="tip"><p>Nous utilisons la bibliothèque Datasets <d-cite bibtex-key="lhoest2021datasets"></d-cite> pour charger et traiter les données que nous utilisons. | |
Avec cette bibliothèque, les données sont décompressées en local au format <code>Arrow</code>. | |
De plus, si les données chargées depuis le Hub d’Hugging Face ont été ajoutées avec la fonction <code>push_to_hub()</code>, | |
alors le jeu de données est par défaut converti au format <code>parquet</code>.</p></div> | |
</ul> | |
<ul> | |
<li class="width_125"><u>Prétokeniser les données</u><br> | |
L'option la plus efficace est probablement de prétokeniser les données afin d'optimiser les accès. | |
C'est-à-dire que la tokenisation a lieu dans une étape préalable et non pas à la volée. | |
</li> | |
<div class="tip"><p>Nous invitions le lecteur à consulter le code | |
<a class="link" href="https://github.com/catie-aq/flashT5/blob/main/examples/minipile/pretokenize_minipile.py">suivant</a> qui | |
illustre la façon dont nous procédons dans notre tutoriel sur le FAT5 appliqué au jeu de données Minipile <d-cite bibtex-key="kaddour2023minipilechallengedataefficientlanguage"></d-cite>.</p></div> | |
</ul> | |
<p><br></p> | |
<h4 id="traitement-des-donn-es">Traitement des données</h4> | |
<p class="width_125">Si la limitation vient du traitement des données après leur chargement :</p> | |
<ul> | |
<li><p class="width_125"><u>Il est possible d’utiliser plusieurs processus pour traiter les données en parallèle</u><br> | |
Par exemple, le paramètre <code>num_workers</code> du <code>Dataloader</code> de PyTorch <d-cite bibtex-key="paszke2019pytorch"></d-cite>.</p></li> | |
<div class="tip"><p>Vous pouvez retrouver dans notre code les valeurs que nous utilisons pour ce paramètre pour notre FAT5 <a class="link" href="https://github.com/catie-aq/flashT5/blob/dfe10d498ae0b39082182f807acb509e91992360/configs/fr/fat5-fr-small.yaml#L42">small</a>.</div> | |
</ul> | |
<ul> | |
<li><p class="width_125"><u>Le goulot d’étranglement peut aussi venir du <code>DataCollator</code></u><br> | |
C'est notamment le cas lorsqu’il y a des tâches complexes à effectuer (masquage d’image ou débruiteurs multiples sur des tâches de NLP).<br> | |
On pourra alors construire un <code>DataCollator</code> personnalisé pour la tâche. | |
On appliquera les méthodes traditionnelles pour optimiser la vitesse de celui-ci. | |
De même, l’emploi de la vectorisation de Numpy permettra de traiter plus rapidement des listes qu'avec des boucles <code>for</code>. | |
D’une manière générale, Numpy est plus rapide que PyTorch pour ce type de tâches. | |
On pourra aussi utiliser des méthodes de compilation comme numba <d-cite bibtex-key="10.1145/2833157.2833162"></d-cite> pour Python par exemple.</p></li> | |
<div class="tip"><p>Nous avons suivi ce principe et développé un <code>DataCollator</code> personnalisé pour notre FAT5 dont vous pouvez consulter le code <a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/data/data_collator_ul2.py">ici</a>. | |
Il gère les tâches de prétexte UL2 tout en ayant un mécanisme de batch dynamique pour réduire le <em>padding</em> (plus d'informations dans la section suivante).</p></div> | |
<div class="note"><p>Comme il n’y avait pas d’implémentation du <code>DataCollator</code> d’UL2 disponible en PyTorch jusqu’ici, nous espérons que cela pourra être utile à d’autres travaux.</p></div> | |
</ul> | |
<ul> | |
<li><p class="width_125"><u>Réaliser un <em>padding</em> efficace</u><br> | |
<p class="width_125">Lorsque l’on travaille avec des séquences, on a naturellement tendance à padder un ensemble de séquences pour pouvoir construire des batchs. | |
Les <em>tokens</em> de <em>padding</em> engendrent alors des calculs inutiles.<br> | |
La première chose à faire est de limiter le <em>padding</em> à la séquence de taille maximum et non à une valeur maximale. | |
C’est la technique du <a class="link" href="https://huggingface.co/learn/nlp-course/chapter3/2?fw=pt#dynamic-padding">padding dynamique</a>.<br> | |
Avec cette technique, il peut néanmoins rester des <em>tokens</em> de <em>padding</em>. Pour les gérer, il existe deux possibilités :<br> | |
• soit utiliser une méthode de groupage des données avec des tailles similaires | |
(par exemple, <a class="link" href="https://huggingface.co/docs/transformers/main_classes/trainer#transformers.TrainingArguments.group_by_length">ce paramètre</a> | |
dans la bibliothèque Transformers <d-cite bibtex-key="wolf2020huggingfaces"></d-cite> ou encore | |
<a class="link" href="https://discuss.huggingface.co/t/how-to-implement-trainers-group-by-length-in-pytorch/9232">en récupérant ce sampler</a> pour PyTorch)<br> | |
• soit concaténer différents exemples dans un DataCollator personnalisé.</p> | |
<div class="tip"><p>Nous avons opté pour la seconde option et nous renvoyons donc le lecteur à nouveau au <a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/data/data_collator_ul2.py">code</a> de notre DataCollator de mélange de débruiteurs (UL2).</p></div> | |
<div class="note"><p>Des heuristiques plus optimisées doivent probablement être mises en place. | |
Nous avons fait un test en proposant une | |
<a class="link" href="https://github.com/catie-aq/flashT5/blob/dfe10d498ae0b39082182f807acb509e91992360/src/data/data_collator_ul2.py#L45">fonction</a> | |
dans le <code>DataCollator</code> afin de trier les <code>input_ids</code> et les <code>labels</code> par longueur décroissante. | |
Néanmoins ceci est plutôt long pour un gain d'empaquetage minime. | |
Un travail plus conséquent serait à effectuer sur ce point. | |
</p></div> | |
</ul> | |
<p class="width_125"><br><br></p> | |
<h3 id="optimisation-de-la-bande-passante-de-la-m-moire-du-gpu">Optimisation de la bande passante de la mémoire du GPU</h3> | |
<p class="width_125">La limitation en bande passante mémoire est plus difficile à traiter. | |
Une opération limitée par la mémoire est une opération dont le temps global d’exécution est restreint par les accès mémoires. | |
C’est notamment le cas pour les LLMs particulièrement au niveau de l’inférence. | |
Le diagnostic peut être posé à partir du <a class="link" href="https://pytorch.org/tutorials/intermediate/tensorboard_profiler_tutorial.html"><em>profiler</em> de PyTorch</a> :</p> | |
<figure class="width_125"> | |
<img src="https://pytorch.org/tutorials/_static/img/profiler_overview1.png" alt="profiler_overview1.png" width="100%"> | |
<figcaption><center><i>Source : <a class="link" href="https://pytorch.org/tutorials/_static/img/profiler_overview1.png">https://pytorch.org/tutorials/_static/img/profiler_overview1.png</a></i></center></figcaption> | |
</figure> | |
<br><br><br> | |
<p class="width_125">Une autre possibilité pour établir un diagnostic est d’utiliser un simple <code>nvidia-smi</code> :</p> | |
<figure class="width_125"> | |
<img src="./assets/nvidiasmi.png" alt="nvidiasmi.png" width="100%"> | |
</figure> | |
<br> | |
<p class="width_125">Utile pour savoir si un problème est présent mais donne peu d’information sur la nature de ce problème. | |
C’est pourquoi nous avons une préférence pour le profiler.</p> | |
<p><br></p> | |
<h4 id="noyau-cuda">Noyau CUDA</h4> | |
<p class="width_125">La technique reine pour optimiser la bande passante de la mémoire du GPU est de développer un kernel CUDA fusionnant | |
dans la SRAM plusieurs opérations limitantes. | |
Cela peut limiter la copie de larges matrices dans l’HBM pour les recharger immédiatement ensuite dans la SRAM. | |
C’est désormais une caractéristique courante des <i>transformers</i> décodeurs grâce à la <a class="link" href="https://github.com/Dao-AILab/flash-attention">Flash Attention</a>.</p> | |
<div class="tip"><p> | |
Comme la Flash Attention ne gère pas les biais attentionnels (additifs) du T5, nous l’avons étendue en développant un noyau CUDA personnalisé. | |
Comme évoqué dans l’introduction, c’est en réalité successivement deux versions successives de ce noyau que nous avons implémentées. | |
Sans pour autant entrer dans les détails des 650 lignes de code de l’implémentation de la première version (consultable | |
<a class="link" href="https://github.com/Dao-AILab/flash-attention/pull/617">ici</a>), | |
l’idée générale et simplifiée (pour une passe avant) est la suivante :</p> | |
<ul> | |
<li>On charge de l’HBM vers la SRAM la sortie attendue O (output) initialisée avec des 0, | |
de même que la requête Q (query), la clé K (key), la valeur V (value) et les biais B.</li> | |
<li>Notre noyau CUDA calcule les étapes suivantes :<br> | |
• Calcul de la matrice S via le produit matriciel entre Q et la transposée de K<br> | |
• Calcul de S’ qui est la somme de la matrice S et de la matrice des biais B<br> | |
• Calcul de P qui est la softmax (cumulative sous le capot) de S’<br> | |
• Calcul de la sortie O qui est le produit matriciel de P par V<br> | |
</li> | |
<li>La sortie O est chargée sur l’HBM et la SRAM est vidée. | |
<picture> | |
<source media="(prefers-color-scheme: dark)" srcset="./assets/FAT5_dark.gif"> | |
<img alt="FAT5 animation" src="./assets/FAT5.gif" width="100%"> | |
</picture> | |
</ul> | |
<br> | |
<p>Alors que la première version du noyau est générique, la seconde (disponible <a class="link" href="https://github.com/Dao-AILab/flash-attention/pull/956">ici</a>) | |
est spécifique au fonctionnement de modèles avec encodage positionnel relatif (ce qui est le cas du T5). | |
L’idée générale et simplifiée (pour une passe avant) est la suivante :</p> | |
<ul> | |
<li>Dans l'HBM, nous avons la sortie attendue O (output) initialisée avec des 0, | |
de même que la requête Q (query), la clé K (key), la valeur V (value). | |
Cependant nous n'avons pas la matrice de biais B comme précédemment mais le bucket de tenseur T.</li> | |
<li>On charge de l’HBM vers la SRAM la sortie attendue O (output) initialisée avec des 0, | |
de même que la requête Q (query), la clé K (key), la valeur V (value) et le bucket de tenseur T.</li> | |
<li>Notre noyau CUDA calcule les étapes suivantes :<br> | |
• Calcul de la matrice S via le produit matriciel entre Q et la transposée de K<br> | |
• Calcul de S’ qui est la somme de la matrice S et d'une matrice remplie avec les éléments de T<br> | |
• Calcul de P qui est la softmax (cumulative sous le capot) de S’<br> | |
• Calcul de la sortie O qui est le produit matriciel de P par V<br> | |
</li> | |
<li>La sortie O est chargée sur l’HBM et la SRAM est vidée.</li> | |
</ul> | |
<p> | |
De cette façon, alors que la première version la matrice de biais B nécessitait une mémoire quadratique, ici nous nous ramenons | |
à une mémoire linéaire permettant d'effectuer des inférences sur des dizaines de milliers de <em>tokens</em>.<br> | |
Pour la conception de cette seconde version, nous nous sommes inspirés du noyau Triton du TurboT5 que nous avons porté en CUDA et étendu au full bf16. | |
</p> | |
</div> | |
<br> | |
<div class="tip"><p>Notons que les deux versions développées peuvent être utilisées avec plusieurs encodages positionnels.<br> | |
Nous invitions le lecteur à consulter ce <a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/utils/positional_encoding.py">fichier</a> | |
contenant des classes compatibles avec la Flash Attention pour le | |
<a class="link" href="https://github.com/catie-aq/flashT5/blob/dfe10d498ae0b39082182f807acb509e91992360/src/utils/positional_encoding.py#L10">RelativePositionalEncoding</a> | |
<d-cite bibtex-key="shaw2018selfattention"></d-cite>, | |
l’<a class="link" href="https://github.com/catie-aq/flashT5/blob/dfe10d498ae0b39082182f807acb509e91992360/src/utils/positional_encoding.py#L113">ALiBiPositionalEncoding</a> | |
<d-cite bibtex-key="press2022train"></d-cite>, | |
le <a class="link" href="https://github.com/catie-aq/flashT5/blob/dfe10d498ae0b39082182f807acb509e91992360/src/utils/positional_encoding.py#L205">RotaryPositionalEncoding</a> | |
<d-cite bibtex-key="su2023roformer"></d-cite> et | |
<a class="link" href="https://github.com/catie-aq/flashT5/blob/dfe10d498ae0b39082182f807acb509e91992360/src/utils/positional_encoding.py#L341">FIRE</a> <d-cite bibtex-key="li2024functional"></d-cite>.</p></div> | |
<div class="note"><p>Au moment de la rédaction de ces lignes, | |
les deux <em>pull request</em> (une pour chaque version du noyau, disponibles <a class="link" href="https://github.com/Dao-AILab/flash-attention/pull/617">ici</a> | |
et <a class="link" href="https://github.com/Dao-AILab/flash-attention/pull/956">ici</a>) ouvertes sur le répertoire officiel de la Flash Attention n’ont pas été mergées. | |
Le lecteur devra donc provisoirement recompiler nos patchs personnalisés de la Flash Attention pour pouvoir utiliser nos modèles.<br> | |
Nous invitons le lecteur à consulter la partie Benchmark plus bas dans l'article pour visualiser l'amélioration apportée par ces deux noyaux.</p></div> | |
<br> | |
<div class="note"><p>Bien que nous n’y ayons pas eu recours, il est à noter que certaines libraires contiennent des implémentations d’opérateurs courant fusionnés, par exemple Apex <d-cite bibtex-key="nvidiapex"></d-cite>.</p></div> | |
<p><br></p> | |
<h4 id="noyau-triton">Noyau Triton</h4> | |
<p class="width_125">Triton <d-cite bibtex-key="10.1145/3315508.3329973"></d-cite> est un langage de programmation maintenu qui permet de compiler du code Python efficacement à l’instar de CUDA mais ayant l’avantage d’être (de notre point de vue) plus simple à prendre en main. En effet, contrairement à CUDA, qui nécessite une compréhension approfondie de l’architecture matérielle des GPU, Triton fait abstraction de nombreux détails de bas niveau tels que la coalescence de la mémoire, la gestion de la mémoire partagée et l’ordonnancement au sein des blocs de threads CUDA.</p> | |
<div class="tip"><p>Une implémentation en Triton de la | |
<a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/model/ops/flash_attention_v2_bias.py">Flash Attention 2 gérant les biais d’attention</a> | |
est fournie pour ceux qui ne souhaitent pas recompiler un patch personnalisé pour la Flash Attention. | |
Pour cela, nous nous sommes appuyés sur le répertoire de FlagAttention <d-cite bibtex-key="flagattention"></d-cite>. | |
<br> | |
<br> | |
En complément de cette implémentation (dont l'usage est facultatif), d’autres parties de l’architecture ont été optimisées à l’aide de noyaux Triton ad hoc, à savoir : | |
<br> | |
• la perte d’<a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/model/ops/cross_entropy_loss.py">entropie croisée</a> (et la perte z <d-cite bibtex-key="debrébisson2016zloss"></d-cite>) <br> | |
• la <a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/model/ops/rms_norm.py">couche de RMSNorm</a> <d-cite bibtex-key="zhang2019root"></d-cite> <br> | |
<br> | |
Nous nous sommes notamment inspirés de <a class="link" href="https://github.com/unslothai/unsloth">Unsloth</a> <d-cite bibtex-key="unsloth"></d-cite>.<br> | |
<br> | |
Nous invitons le lecteur se référer à la partie Benchmark plus bas dans l'article pour visualiser l'impact de cette optimisation.</div> | |
<p><br></p> | |
<h4 id="utiliser-torch-compile-">Utiliser <code>torch.compile</code></h4> | |
<p class="width_125">Une approche plus simple est de compiler les modèles avec <code>torch.compile</code>. | |
PyTorch se charge alors de faire les fusions possibles, éventuellement en réordonnant des opérations. | |
Il s’agit alors de faire la chasse aux cassures dans le graphe de compilation qui sont des retours à un mode d’exécution « eager » impactant | |
négativement la performance de l’opération.</p> | |
<div class="note"><p>Voir la <a class="link" href="https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html">documentation</a> officielle pour plus de détails.</p></div> | |
<p class="width_125">Une autre possibilité consiste à un usage conjoint d’un noyau kernel personnalisé et de <code>torch.compile</code>. | |
L'implémentation de cette option est grandement simplifiée depuis la | |
<a class="link" href="https://github.com/pytorch/pytorch/releases/tag/v2.4.0">version 2.4 de PyTorch</a>.</p> | |
<p class="width_125">Nous invitons le lecteur à se référer à la partie benchmark disponible plus bas dans l’article afin de mesurer les performances mémoire | |
des différentes techniques décrites.</p> | |
<p class="width_125"><br><br></p> | |
<h3 id="optimisation-de-l-utilisation-des-tensor-cores">Optimisation de l’utilisation des Tensor Cores</h3> | |
<p class="width_125">Les GPU récents possèdent des unités dédiées aux opérations tensorielles : les TensorCore. Les utiliser correctement est essentiel.</p> | |
<p class="width_125">A nouveau, pour établir un diagnostic, il est conseillé de se référer au profiler de PyTorch qui indique la proportion de TensorCore | |
utilisé pour chaque noyau CUDA :</p> | |
<p><figure class="width_125"> | |
<img src="https://pytorch.org/tutorials/_static/img/profiler_kernel_view.png" alt="profiler_kernel_view.png" width="100%"> | |
<figcaption><center><i>Source : <a class="link" href="https://pytorch.org/tutorials/_static/img/profiler_kernel_view.png">https://pytorch.org/tutorials/_static/img/profiler_kernel_view.png</a></i></center></figcaption> | |
</figure> | |
<br><br> | |
<p class="width_125">Concernant les optimisations réalisables :<br></p> | |
<h4 id="puissances-de-2">Multiple de 8 ou de 64</h4> | |
<p class="width_125">La première consiste à employer des tailles de tenseurs de certains multiples de 8 ou de 64. | |
Nous invitons le lecteur à se référer à la documentation de Nvidia, | |
en particulier cet <a class="link" href="https://docs.nvidia.com/deeplearning/performance/dl-performance-matrix-multiplication/index.html#requirements-tc">article</a> | |
et cet <a class="link" href="https://developer.nvidia.com/blog/optimizing-gpu-performance-tensor-cores/">article</a> pour déterminer le multiple à sélectionner en fonction de la précision désirée. </p> | |
<div class="tip"><p>Dans cette logique, nous avons entraîné un <em>tokenizer</em> de taille 32 768 (8**5), suivant <a class="link" href="https://twitter.com/karpathy/status/1621578354024677377">cette observation de KARPATHY</a>. | |
Il s'agit d'un BPE <d-cite bibtex-key="sennrich2016neuralmachinetranslationrare"></d-cite> entraîné sur CulturaX et The Stack, utilisant 256 extra_tokens et | |
les chiffres sont séparés.<br> | |
Le lecteur pourra trouver le code utilisé <a class="link" href=https://github.com/catie-aq/flashT5/blob/main/examples/fat5-fr/train_tokenizer.py">ici</a>. | |
</p></div> | |
<p><br></p> | |
<h4 id="utiliser-le-bon-optimiseur">Utiliser le bon optimiseur</h4> | |
<p class="width_125">Changer d’optimiseur par rapport à l’implémentation initiale du modèle peut être judicieux pour accélérer la convergence du modèle | |
(cela peut néanmoins empêcher la reproduction des résultats du papier original).<br> | |
Les optimiseurs accélèrent la convergence en permettant des tailles de batchs conséquentes comme dans le cas de LAMB <d-cite bibtex-key="you2020large"></d-cite> | |
ou l’utilisation de <em>learning rates</em> plus élevés comme Sophia <d-cite bibtex-key="liu2024sophia"></d-cite>.<br> | |
Des versions plus efficaces des optimiseurs peuvent être aussi utilisées comme l’option <code>fused</code> | |
dans l’<a class="link" href="https://pytorch.org/docs/stable/generated/torch.optim.Adam.html">optimiseur Adam</a> de PyTorch | |
ou encore les optimiseurs disponibles dans <a class="link" href="https://github.com/NVIDIA/apex">Apex</a>.</p> | |
<div class="tip"><p> | |
Nous avons utilisé l’optimiseur l’optimiseur original du T5, <a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/utils/adamw_scaled.py">AdamWScale</a>. | |
Pour les valeurs des hyperparamètres, nous utilisons <code>lr = 5e-3</code>, <code>betas = (0.9, 0.999)</code>, <code>eps = 1e-6</code> et <code>weight_decay = 0.0</code> | |
en nous basant sur les observations de <a class="link" href="https://github.com/PiotrNawrot/nanoT5/issues/25#issuecomment-1922731400">Wilson Wongso</a>. | |
En effet, il s'avère que tous les optimiseurs alternatifs testés ne convergeaient pas.</p></div> | |
<div class="note"><p>Nous avons fait en sorte que notre version d'AdamWScale dispose du paramètre <code>foreach</code>.</p> | |
</div> | |
<p><br></p> | |
<h4 id="entra-ner-ses-mod-les-en-bf16-ou-fp16-">Entraîner ses modèles en <code>bf16</code></h4> | |
<p class="width_125">Les GPU récents permettent d’exploiter pleinement l’utilisation de précision réduite | |
(permettant de gagner un facteur 2 de débit par rapport au <code>fp32</code>). | |
Le <code>bf16</code> n’est disponible que sur les architectures Ampere ou plus récentes mais autorise de s’affranchir de méthode | |
de <em>loss scaling</em> <d-cite bibtex-key="micikevicius2018mixed"></d-cite> qui est généralement nécessaire en <code>fp16</code> | |
grâce à une plage dynamique plus grande (l’exposant est codé sur 8 bits comme le <code>fp32</code>).</p> | |
<div class="tip"><p>Dans cette logique, nous entraînons nos modèles en <code>bf16</code>. | |
Plus précisément, alors qu'au début de nos expérimentations nous utilisions du <code>bf16-mixed</code>, nous avons recouru à | |
<a class="link" href="https://en.wikipedia.org/wiki/Kahan_summation_algorithm">la sommation compensée de Kahan</a> | |
afin de pouvoir utiliser du <code>full bf16</code> dans notre optimiseur.<br> | |
A nouveau, le code de notre optimiseur est consultable <a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/utils/adamw_scaled.py">ici</a>.</p> | |
</div> | |
<h4 id="utiliser-moins-de-m-moire-du-gpu">Utiliser moins de mémoire GPU</h4> | |
<p class="width_125">Certaines techniques existent pour limiter l’utilisation de mémoire GPU par le modèle tel que le | |
<a class="link" href="https://pytorch.org/docs/stable/checkpoint.html"><em>gradient checkpointing</em></a> | |
ou les méthodes type ZeRO <d-cite bibtex-key="rajbhandari2020zero"></d-cite> implémentées dans | |
<a class="link" href="https://github.com/microsoft/DeepSpeed">DeepSpeed</a>. | |
En limitant la mémoire utilisée, des tailles de batchs plus grandes peuvent être utilisées accélérar l’entraînement du modèle.</p> | |
<p class="width_125"><br><br></p> | |
<h3 id="autres">Autres</h3> | |
<h4 id="le-parall-lisme">Le parallélisme</h4> | |
<p class="width_125">L’utilisation de plusieurs GPUs est délicate. | |
Réalisée naïvement, elle peut résulter en des performances inférieures à l’implémentation sur un seul GPU gâchant alors des ressources de calculs. | |
C’est le cas notamment lorsque des goulets d’étranglement se forment au niveau des communications entre les GPU. | |
Il s’agit d’être sûr que le modèle n’est pas limité par la bande passante entre les cartes ou de s'assurer que les cartes sont connectées avec des | |
bandes passantes suffisantes via des techniques type <a class="link" href="https://en.wikipedia.org/wiki/NVLink">NVLink</a> par exemple. </p> | |
<p class="width_125">A noter aussi que les techniques d’optimisation requièrent en général de synchroniser tous les GPU à la fin d’un batch. | |
De ce fait, si un GPU est plus lent que les autres (ou est utilisé par un autre processus), le modèle est bridé à la vitesse du GPU le plus lent de l’ensemble. </p> | |
<div class="note"><p> | |
Ayant pré-entraîné notre modèle sur une seule A100 80Go, nous n'avons pas pu expérimenter le parallélisme.</p> | |
</div> | |
<p><br></p> | |
<h4 id="les-t-tes-pour-le-finetuning">Les têtes pour le finetuning</h4> | |
<p class="width_125">Nous nous sommes penchés sur les éléments listés ci-dessus dans une optique d’optimiser le pré-entraînement de notre modèle. | |
En pratique, nous devons ensuite le finetuner pour le spécialiser sur les tâches finales qui nous intéresse. | |
Pour cela, nous recourrons à des têtes. Pour le <a class="link" href="https://huggingface.co/docs/transformers/model_doc/t5">T5 « standard »</a>, | |
cinq sont disponibles dans Transformers permettant d’effectuer toutes les tâches faisables : | |
<a class="link" href="https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5ForConditionalGeneration"><code>T5ForConditionalGeneration</code></a>, | |
<a class="link" href="https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5ForSequenceClassification"><code>T5ForSequenceClassification</code></a>, | |
<a class="link" href="https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5ForTokenClassification"><code>T5ForTokenClassification</code></a>, | |
<a class="link" href="https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5ForQuestionAnswering"><code>T5ForQuestionAnswering</code></a> | |
et <a class="link" href="https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5EncoderModel"><code>T5EncoderModel</code></a>.<br><br> | |
Là encore, un travail d’optimisation peut être effectué.<br> | |
Pour la génération conditionnelle, le principal point est de s’assurer d’avoir un processus de génération efficace.<br> | |
Pour les têtes portant sur des tâches de classification (séquence, NER et QA), il faut s’assurer que l’on utilise la partie encodeur | |
du T5 puisque le décodeur n’est pas essentiel pour celles-ci comme le montre l’EncT5 <d-cite bibtex-key="liu2022enct5"></d-cite>. | |
En effet, les poids du décodeur prennent inutilement de la place en mémoire, et le temps d’exécution du code du finetuning est doublé inutilement.<br> | |
La dernière tête sert simplement à ne garder que la partie encodeur d'un modèle encodeur-décodeur. Elle n'a donc pas besoin d'être optimisée.</p> | |
<div class="tip"><p> | |
Concernant la tête <code>ForConditionalGeneration</code>, notre | |
<a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/model/modeling_flash_t5.py">implémentation</a> | |
repose sur le processus de génération disponible dans le | |
<a class="link" href="https://github.com/PiotrNawrot/nanoT5/blob/1c82d67bf8dea635be68a3b2a68a43b68b665193/nanoT5/utils/t5_model.py#L407">nanoT5</a> | |
car est 14% plus rapide que l’implémentation d’Hugging Face.<br> | |
Concernant les têtes de classification, l’implémentation est disponible dans ce | |
<a class="link" href="https://github.com/catie-aq/flashT5/blob/main/src/model/custom_heads_flash_t5.py">fichier</a>. | |
Il s'agit d'un fichier disjoint du fichier modeling car nos implémentations diffèrent de celles disponibles dans Transformers. | |
En effet, les implémentations des têtes <code>T5ForSequenceClassification</code> et de <code>T5ForQuestionAnswering</code> disponibles dans Transformers reposent | |
sur l’encodeur et le décodeur du T5 ce qui est donc inefficient. | |
Nous avons donc recodé ces deux têtes pour n'utiliser que l'encodeur. | |
Nous avons alors suivi la même structure que la tête <code>T5ForTokenClassification</code> disponible dans Transformers qui, | |
utilise aussi que l'encodeur et donc reprenons telle quelle. </p> | |
</div> | |
<p class="width_125"><br><br><br></p> | |
<h2 id="benchmark">Benchmark</h2> | |
<h3 id="TFLOPS">TFLOPS</h3> | |
<p class="width_125"> | |
Le nombre de TFLOPS (trillions de calculs en virgule flottante qu'un processeur peut effectuer en une seconde) est probablement la mesure la plus parlante | |
pour étayer l'impact des optimisations effectuées.<br> | |
Nous comparons alors quatre approches :<br> | |
• l'implémentation SPDA (Scaled Dot Product Attention) avec full bias,<br> | |
• la même implémentation mais en Triton,<br> | |
• l'implémentation en Flash Attention RPE, c'est-à-dire le second noyau que nous avons développé (peut être vu comme le turboT5 mais en C++/Cuda avec bf16 full),<br> | |
• l'implémentation en Flash Attention i.e. sans biais. Nous l'indiquons pour avoir une référence car elle est inutilisable en pratique pour un T5.<br> | |
<br> | |
Pour la passe avant, nous avons : | |
</p> | |
<p class="width_125"> | |
<picture> | |
<source media="(prefers-color-scheme: dark)" srcset="./assets/FWD-causal-True_dark.png" width="100%"> | |
<img alt="Benchmark memory backward pass" src="./assets/FWD-causal-True.png" width="100%"> | |
</picture> | |
<div class="width_125"><p>Pour la passe avant, nous pouvons observer que l'approche en Triton permet 1,34 fois plus de FLOPS que celle en SPDA et que l'approche en Flash Attention RPE permet 1,99 fois plus de FLOPS que celle en SPDA.<br> | |
Nous pouvons aussi constater que notre implémentation en bf16 est équivalente à du fp16 (faisant même mieux en taille 512).<br> | |
C'est suite à ce benchmark que nous avons décidé d'entraîner notre modèle en français en bf16, head_dim = 128 et avec une séquence 1024.</p></div> | |
<br> | |
<p class="width_125"> | |
Pour la passe arrière, nous avons : | |
</p> | |
<p class="width_125"> | |
<picture> | |
<source media="(prefers-color-scheme: dark)" srcset="./assets/BWD-causal-True_dark.png" width="100%"> | |
<img alt="Benchmark memory backward pass" src="./assets/BWD-causal-True.png" width="100%"> | |
</picture> | |
<div class="width_125"><p>Pour la passe arrière, l'implémentation en Triton se révèle moins efficace que SPDA avec 0,71 fois les FLOPS de SPDA. Celle en Flash Attention RPE est plus ou moins équivalente à SPDA (1,018 fois plus de FLOPS).<br> | |
Nous pouvons également observer que Triton en head_dim 64 est plus efficace que Triton en head_dim 128.</p></div> | |
<p><br></p> | |
<h3 id="torchvstriton">Torch vs Triton</h3> | |
<p class="width_125"> | |
Nous indiquions plus haut avoir optimisé des parties de l’architecture à l’aide de noyaux Triton ad hoc, à savoir l'entropie croisée et la couche de RMSNorm. | |
Les benchmarks suivants doivent en illustrer la raison.<br> | |
Pour l'entropie croisée, nous obtenons une passe avant 7 à 11,4 fois plus rapide, une passe arrière 3,26 à 3,75 plus rapide ainsi qu'une mémoire réduite d'un facteur 4 :</p> | |
<p class="width_125"> | |
<picture> | |
<source media="(prefers-color-scheme: dark)" srcset="./assets/CE_dark.png" width="100%"> | |
<img alt="Benchmark memory backward pass" src="./assets/CE.png" width="100%"> | |
</picture> | |
<p class="width_125"> | |
Pour la couche de RMSNorm, nous obtenons une passe avant 3 à 5 fois plus rapide, une passe arrière 2,33 à 4,33 plus rapide ainsi qu'une mémoire réduite d'un facteur 3,2 :</p> | |
<p class="width_125"> | |
<picture> | |
<source media="(prefers-color-scheme: dark)" srcset="./assets/LN_dark.png" width="100%"> | |
<img alt="Benchmark memory backward pass" src="./assets/LN.png" width="100%"> | |
</picture> | |
<p class="note"> | |
Notez que l'ensemble des graphiques des benchmarks peuvent être générés automatiquement via le code <a href="https://github.com/catie-aq/flashT5/tree/main/benchmarks">suivant</a>. | |
</p> | |
<p><br><br></p> | |
<h3 id="mod-le-en-fran-ais">Modèle en français</h3> | |
<p class="width_125">Nous avons appliqué notre travail au français en pré-entraînant un modèle de 147M de paramètres. <br> | |
Le jeu de données que nous avons utilisé est un mélange de CulturaX, Wikipedia, justice_fr et The Stack. <br> | |
Notre <em>tokenizer</em> de taille 32 768 (8**5) est entraîné sur CulturaX et The Stack.<br> | |
Notre modèle est pré-entraîné sur une séquence de 1 024 tokens.</p> | |
<p class="width_125"> | |
Nous souhaitions comparer les performances de notre modèle face à d'autres modèles en français précédemment publiés comme le CamemBERT <d-cite bibtex-key="Martin_2020"></d-cite> pour les tâches de classification et le BARThez <d-cite bibtex-key="eddine2021barthez"></d-cite> pour les tâches de génération.<br> | |
Pour cela, il nous est paru important de faire des comparaisons à nombre de <i>tokens</i> vus équivalent. | |
Nous avons ainsi essayé d'estimer le nombre de <i>tokens</i> vus par ces deux modèles via la formule nombre de steps × la taille de la séquence × la taille du batch. Nous n'avons pas trouvé les informations dans la publication du BARThez pour le faire. Pour le CamemBERT nous l'estimons à environ 419,4 Mds de <i>tokens</i> au maximum. Ce chiffre pourrait être en réalité moins élevé car nous ne connaissons pas le nombre de <i>tokens</i> de <i>padding</i> vus par ce modèle (là où dans notre cas, nous n'en utilisons pas). Ainsi, nous avons pré-entraîné notre modèle sur le nombre maximal de <i>tokens</i> vus par le CamemBERT.<br></p> | |
<p><br></p> | |
<p class="width_125"> | |
<picture> | |
<source media="(prefers-color-scheme: dark)" srcset="./assets/loss_train.png" width="49%"> | |
<img alt="Convergence masked accuracy FAT5" src="./assets/loss_train.png" width="49%"> | |
</picture> | |
<picture> | |
<source media="(prefers-color-scheme: dark)" srcset="./assets/loss_eval.png" width="49%"> | |
<img alt="Convergence masked accuracy FAT5" src="./assets/loss_eval.png" width="49%"> | |
</picture> | |
</p> | |
<p><br></p> | |
<p class="width_125"> | |
Nous nous sommes également intéressés à comparer notre modèle face à lui-même, c'est-à-dire que nous avons évalué ses performances sur les tâches en aval toutes les 100 000 steps (~26 Mds de tokens) au cours du pré-entraînement.<br> | |
Dans le tableau ci-dessous, nous avons listés le nombre de <i>tokens</i> équivalents à chaque intervalle de 100 000 steps.<br> | |
</p> | |
<table class="width_125"> | |
<thead> | |
<tr> | |
<th>Modèle</th> | |
<th>Nombre de <em>tokens</em> ✝</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>FAT5-small-100K</td> | |
<td>26 214 400 000 (100 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-200K</td> | |
<td>52 428 800 000 (200 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-300K</td> | |
<td>78 643 200 000 (300 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-400K</td> | |
<td>104 857 600 000 (400 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-500K</td> | |
<td>131 072 000 000 (500 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-600K</td> | |
<td>157 286 400 000 (600 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-700K</td> | |
<td>183 500 800 000 (700 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-800K</td> | |
<td>209 715 200 000 (800 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-900K</td> | |
<td>235 929 600 000 (900 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1000K</td> | |
<td>262 144 000 000 (1 000 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1100K</td> | |
<td>288 358 400 000 (1 100 000× 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1200K</td> | |
<td>314 572 800 000 (1 200 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1300K</td> | |
<td>340 787 200 000 (1 300 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1400K</td> | |
<td>367 001 600 000 (1 400 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1500K</td> | |
<td>393 216 000 000 (1 500 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1600K</td> | |
<td>419 430 400 000 (1 600 000 × 1024 × 256)</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="https://hf.co/almanach/camembert-base">camembert (base ou large)</a></td> | |
<td>419 430 400 000 (100 000 × 512 × 8192)</td> | |
</tr> | |
</tbody> | |
</table> | |
<p class="width_125">✝ équivaut au nombre de steps × la taille de la séquence × la taille du batch</p> | |
<p><br></p> | |
<h4 id="finetuning">Finetuning</h4> | |
<p class="width_125">Nous nous sommes focalisés sur cinq tâches :<br> | |
• Du résumé de textes pour illustrer un usage de la tête <code>T5ForConditionalGeneration</code>,<br> | |
• De la classification binaire pour illustrer un usage de la tête <code>T5ForSequenceClassification</code>,<br> | |
• De la reconnaissance d’entités nommées pour illustrer un usage de la tête <code>T5ForTokenClassification</code>,<br> | |
• Du question answering pour illustrer un usage de la tête <code>T5ForQuestionAnswering</code>.<br> | |
• De la <i>sentence similarity</i> pour illustrer un usage de la tête <code>T5EncoderModel</code>.</p> | |
<p class="width_125"> Les tâches de classification nous semblent être importantes à évaluer car elles sont généralement ignorées par les benchmarks des grands modèles de langue génératifs alors qu’il de tâches fréquemment utilisées en pratique par les entreprises (recherche documentaire, classification pour d'avis clients, anonymisation de données, etc.). | |
En témoigne sûrement le fait que 6 ans et demi après sa sortie, BERT <d-cite bibtex-key="devlin2019bert"></d-cite> est téléchargé plus de fois par mois à lui seul que les <a class="link" href="https://huggingface.co/models?pipeline_tag=text-generation&sort=downloads">30 modèles de génération de textes</a> les plus téléchargés sur Hugging Face au moment où nous écrivons ces lignes : 38,5M contre 31,3M.</p> | |
<p class="width_125">Dans les tableaux suivants, nous soulignons pour le FAT5 la ligne obtenant le meilleur résultat pour chacune des tâches. Nous interprétons les résultats de la partie génération après le tableau sur le résumé de texte. Les résultats sur la partie classification sont interprétés après l'ensemble des tableaux de classification binaire, QA, NER et de <i>sentence-similarity</i>.</p> | |
<p><br></p> | |
<h5>Résumé de textes</h5> | |
<p class="width_125">Pour cette tâche, nous avons utilisé le jeu de données <a class="link" href="https://huggingface.co/datasets/orange_sum">orange_sum</a><d-cite bibtex-key="eddine2021barthez"></d-cite>.</p> | |
<table class="width_125"> | |
<thead> | |
<tr> | |
<th>Modèle</th> | |
<th>ROUGE-1</th> | |
<th>ROUGE-2</th> | |
<th>ROUGE-L</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>FAT5-small-100K (147M)</td> | |
<td>28,17</td> | |
<td>10,60</td> | |
<td>20,62</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-200K (147M)</td> | |
<td>28,72</td> | |
<td>10,86</td> | |
<td>20,68</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-300K (147M)</td> | |
<td>28,76</td> | |
<td>10,85</td> | |
<td>20,63</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-400K (147M)</td> | |
<td>28,59</td> | |
<td>10,76</td> | |
<td>20,60</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-500K (147M)</td> | |
<td>28,98</td> | |
<td>10,97</td> | |
<td>20,72</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-600K (147M)</td> | |
<td>29,04</td> | |
<td>11,20</td> | |
<td>20,89</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-700K (147M)</td> | |
<td>28,72</td> | |
<td>10,87</td> | |
<td>20,77</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-800K (147M)</td> | |
<td>29,00</td> | |
<td>10,91</td> | |
<td>20,78</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-900K (147M)</td> | |
<td>29,30</td> | |
<td>11,34</td> | |
<td>21,22</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1000K (147M)</td> | |
<td>29,10</td> | |
<td>11,21</td> | |
<td>21,08</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1100K (147M)</td> | |
<td>29,43</td> | |
<td>11,40</td> | |
<td>21,15</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1200K (147M)</td> | |
<td>29,30</td> | |
<td>11,38</td> | |
<td>21,18</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1300K (147M)</td> | |
<td>29,38</td> | |
<td>11,38</td> | |
<td>21,18</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1400K (147M)</td> | |
<td>29,29</td> | |
<td>11,18</td> | |
<td>21,14</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1500K (147M)</td> | |
<td><u>29,48</u></td> | |
<td><u>11,48</u></td> | |
<td><u>21,22</u></td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1600K (147M)</td> | |
<td>29,30</td> | |
<td>11,27</td> | |
<td>21,10</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="https://huggingface.co/moussaKam/barthez">Barthez<d-cite bibtex-key="eddine2021barthez"></d-cite></a> (165M)</td> | |
<td>31.44</td> | |
<td>12.77</td> | |
<td>22.23</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="https://huggingface.co/moussaKam/mbarthez">mBarthez</a> (458M)</td> | |
<td>32.67</td> | |
<td>13.73</td> | |
<td>23.18</td> | |
</tr> | |
</tbody> | |
</table> | |
<p><br></p> | |
<p class="width_125">Nous pouvons constater que notre modèle performe moins bien que le Barthez. Nous pouvons émettre quelques hypothèses à ce sujet. <br> | |
Premièrement, il est vraisemblable que notre processus de génération de texte ne soit pas optimal. Ne connaissant pas celui utilisé par le Barthez, nous avons simplement recouru aux paramètres par défaut de la fonction <a class="link" href="https://github.com/huggingface/transformers/blob/241c04d36867259cdf11dbb4e9d9a60f9cb65ebc/src/transformers/generation/utils.py#L1905">generate</a> de Hugging Face pour ne pas avantager notre modèle avec un processus de génération qui serait plus sophistiqué.<br> | |
Deuxièmement, nous n'avons pas utilisé de <i>prompt</i> pour conditionner la génération, ce qui aurait pu bénéficier à notre modèle puisque le T5 est le modèle ayant introduit ce système.<br> | |
Troisièmement, le Barthez a sûrement vu plus de <i>tokens</i> que notre modèle. Bien que nous n'arrivons pas à déterminer ce nombre d'après la publication des auteurs, il est indiqué que c'est un modèle BART <d-cite bibtex-key="lewis2019bartdenoisingsequencetosequencepretraining"></d-cite> qui a reçu un pré-entraînement supplémentaire sur du français. Or dans le papier de BART, il est indiqué que le modèle a été entraîné sur 500000 steps × une séquence de 1 024 <i>tokens</i> × un batch de taille 8000, soit 4 096 000 000 000 <i>tokens</i>, ce qui 9,76 fois plus que notre modèle. | |
</p> | |
<p><br></p> | |
<h5 id="classification">Classification</h5> | |
<p class="width_125">Nous utilisons une version nettoyée du jeu de données allocine <d-cite bibtex-key="allocine"></d-cite> : <a class="link" href="https://huggingface.co/datasets/CATIE-AQ/allocine_clean">allocine_clean</a>. Plus précisément 0,6 % de l'échantillon de test n’était pas fiable car contenait des fuites ou des données dupliquées. Il est probable que le jeu de données obtenu soit encore imparfait avec notamment des problèmes d'annotations nécessitant un travail de relecture/correction plus important. | |
</p> | |
<table class="width_125"> | |
<thead> | |
<tr> | |
<th>Modèle</th> | |
<th>Accuracy</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>FAT5-small-100K (67,4M)</td> | |
<td>96,05</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-200K (67,4M)</td> | |
<td>96,20</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-300K (67,4M)</td> | |
<td>96,48</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-400K (67,4M)</td> | |
<td>96,60</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-500K (67,4M)</td> | |
<td>96,60</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-600K (67,4M)</td> | |
<td>96,60</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-700K (67,4M)</td> | |
<td>96,68</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-800K (67,4M)</td> | |
<td>96,59</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-900K (67,4M)</td> | |
<td><u>96,75</u></td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1000K (67,4M)</td> | |
<td>96,62</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1100K (67,4M)</td> | |
<td>96,69</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1200K (67,4M)</td> | |
<td>96,71</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1300K (67,4M)</td> | |
<td>96,69</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1400K (67,4M)</td> | |
<td>96,65</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1500K (67,4M)</td> | |
<td>96,57</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1600K (67,4M)</td> | |
<td>96,69</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="">distilcamembert</a> (68,1M)</td> | |
<td>96,74</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="https://huggingface.co/bourdoiscatie/camembert_base_cls">camembert-base</a> (111M)</td> | |
<td>97,27</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="https://huggingface.co/bourdoiscatie/camembert_large_cls">camembert-large</a> (337M)</td> | |
<td>97,15</td> | |
</tr> | |
<tr> | |
</tbody> | |
</table> | |
<p class="width_125">Note : dans le tableau et dans les suivants, distilcamembert se réfère au <a class="link" href="https://huggingface.co/cmarkea/distilcamembert-base">distilcamembert-base</a> <d-cite bibtex-key="delestre2022distilcamembert"></d-cite> que nous avons finetuné.</p> | |
<p><br></p> | |
<h5>Reconnaissance d’entités nommées</h5> | |
<p class="width_125">Pour cette tâche, nous avons utilisé frenchNER dans sa configuration <a class="link" href="https://huggingface.co/datasets/CATIE-AQ/frenchNER_4entities">4 entités</a> (PER, LOC, ORG, MISC) <d-cite bibtex-key="frenchNER2024"></d-cite>.</p> | |
<table class="width_125"> | |
<thead> | |
<tr> | |
<th>Modèle</th> | |
<th>F1 PER</th> | |
<th>F1 LOC</th> | |
<th>F1 ORG</th> | |
<th>F1 MISC</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>FAT5-small-100K (67,1M)</td> | |
<td>96,51</td> | |
<td>94,48</td> | |
<td>87,24</td> | |
<td>75,81</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-200K (67,1M)</td> | |
<td>96,90</td> | |
<td>94,83</td> | |
<td>88,78</td> | |
<td>76,82</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-300K (67,1M)</td> | |
<td>97,25</td> | |
<td>95,11</td> | |
<td>88,86</td> | |
<td><u>77,48</u></td> | |
</tr> | |
<tr> | |
<td>FAT5-small-400K (67,1M)</td> | |
<td>97,18</td> | |
<td>95,08</td> | |
<td>89,11</td> | |
<td>77,42</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-500K (67,1M)</td> | |
<td>97,25</td> | |
<td>95,16</td> | |
<td>89,16</td> | |
<td>76,91</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-600K (67,1M)</td> | |
<td>97,19</td> | |
<td>95,19</td> | |
<td>88,85</td> | |
<td>76,88</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-700K (67,1M)</td> | |
<td>97,17</td> | |
<td>95,14</td> | |
<td>89,39</td> | |
<td>76,82</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-800K (67,1M)</td> | |
<td><u>97,34</u></td> | |
<td>95,20</td> | |
<td>89,18</td> | |
<td>77,27</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-900K (67,1M)</td> | |
<td>97,19</td> | |
<td>95,21</td> | |
<td>89,04</td> | |
<td>76,83</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1000K (67,1M)</td> | |
<td>97,31</td> | |
<td>95,26</td> | |
<td>89,24</td> | |
<td>76,84</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1100K (67,1M)</td> | |
<td>97,11</td> | |
<td>94,99</td> | |
<td>88,52</td> | |
<td>76,30</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1200K (67,1M)</td> | |
<td>97,19</td> | |
<td>95,11</td> | |
<td>88,79</td> | |
<td>76,86</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1300K (67,1M)</td> | |
<td>97,15</td> | |
<td>95,00</td> | |
<td>88,62</td> | |
<td>76,58</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1400K (67,1M)</td> | |
<td>97,22</td> | |
<td>95,09</td> | |
<td>89,01</td> | |
<td>77,00</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1500K (67,1M)</td> | |
<td>97,32</td> | |
<td><u>95,34</u></td> | |
<td><u>89,39</u></td> | |
<td>77,30</td> | |
</tr> | |
<tr> | |
<td>FAT5-small-1600K (67,1M)</td> | |
<td>97,14</td> | |
<td>95,22</td> | |
<td>89,24</td> | |
<td>76,88</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="">distilcamembert</a> (67,5M)</td> | |
<td>97,26</td> | |
<td>95,24</td> | |
<td>89,10</td> | |
<td>79,88</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="https://huggingface.co/CATIE-AQ/NERmembert-base-4entities">camembert-base</a> (110M)</td> | |
<td>97,80</td> | |
<td>95,78</td> | |
<td>90,27</td> | |
<td>81,38</td> | |
</tr> | |
<tr> | |
<td><a class="link" href="https://huggingface.co/CATIE-AQ/NERmembert-large-4entities">camembert-large</a> (336M)</td> | |
<td>98,17</td> | |
<td>96,37</td> | |
<td>91,87</td> | |
<td>83,35</td> | |
</tr> | |
</tbody> | |
</table> | |
<p><br></p> | |
<h5 id="question-answering">Question Answering</h5> | |
<p class="width_125"> | |
Nous avons voulu finetuner notre modèle sur cette tâche mais nous nous sommes rendu compte que notre tokenizer a deux problèmes.<br> | |
Premièrement, nous avons oublié d'ajouter le token de début de phrase. | |
Deuxièmement, nous avons décidé d'utiliser un fast BPE tokenizer. Nous avons appris après coup que l'argument `add_special_tokens=True` ne fonctionne pas avec ce type de tokenizer. | |
Corriger ces deux points nécessite de post-traiter les encodages du tokenizer avant d'effectuer notre tâche de finetuning ce qui n'est pas élégant et nécessite du temps que nous n'avons pas dans l'immédiat. | |
<p><br></p> | |
<h5><i>Sentence Similarity</i></h5> | |
<p class="width_125"> | |
Nous invitons le lecteur à prendre les résultats de cette section avec des pincettes.<br> | |
Nous avons effectué un finetuning sur cette tâche afin de vérifier que la tête <code>T5EncoderModel</code> fonctionnait | |
mais nous ne nous focalisons pas sur les résultats obtenus car nous nous interrogeons sur la qualité du benchmark sur lequel nous évaluons les modèles, | |
à savoir MTEB FR <d-cite bibtex-key="ciancone2024mtebfrenchresourcesfrenchsentence"></d-cite>, une version française de MTEB.<br> | |
En effet, Nils Reimers, créateur du MTEB, a récemment remis en cause dans un <a class="link" href="https://x.com/Nils_Reimers/status/1870812625505849849">tweet</a> | |
la pertinence de ce benchmark, le déclarant « mort ». | |
Plus tôt dans l'année, nous avions d'ailleurs observé des fuites de données et des duplications dans ce benchmark | |
(voir <a class="link" href="https://huggingface.co/datasets/lbourdois/MTEB_leaks_and_duplications">ici</a> et | |
<a class="link" href="https://github.com/embeddings-benchmark/mteb/issues/1036">ici</a>). | |
Alexey Vatolin a ensuite étendu ces observations en prenant également en compte les lignes vides (voir <a class="link" href="https://github.com/embeddings-benchmark/mteb/issues/1049#issuecomment-2463095122">ici</a>). | |
<br> | |
Dans le tableau ci-dessous, nous finetunons sur une version nettoyée du jeu de données <code>stsb_multi_mt</code> <d-cite bibtex-key="huggingface:dataset:stsb_multi_mt"></d-cite> (0,653 % du test split n'était pas fiable parce qu'il contenait des fuites ou des données dupliquées) avant d'évaluer sur MTEB FR. | |
<br> | |
</p> | |
<table class="width_125"> | |
<thead> | |
<tr> | |
<th>Modèle</th> | |
<th>Moyenne</th> | |
<th>Classification</th> | |
<th>Clustering</th> | |
<th>PairClassification</th> | |
<th>Reranking</th> | |
<th>Retrieval</th> | |
<th>STS</th> | |
<th>Summary</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>FAT5-small-400K (67,1M)</td> | |
<td>52,2</td> | |
<td>59,8</td> | |
<td>39,1</td> | |
<td>77,5</td> | |
<td>56,1</td> | |
<td>29,1</td> | |
<td>74</td> | |
<td>29,8</td> | |
</tr> | |
<tr> | |
<td>distilcamembert(68,1M)</td> | |
<td>51,3</td> | |
<td>60,7</td> | |
<td>37,4</td> | |
<td>77</td> | |
<td>51,1</td> | |
<td>25,2</td> | |
<td>76,4</td> | |
<td>31,3</td> | |
</tr> | |
</tbody> | |
</table> | |
<p><br><br><br></p> | |
<p class="width_125"> | |
Nous observons dans le graphique de la convergence de la <i>masked accuracy</i>, que les performances de la partie encodeur du modèle progressent dans un premier temps avant de s'aplatir. | |
</p> | |
<p><br></p> | |
<p class="width_125"> | |
<picture> | |
<source media="(prefers-color-scheme: dark)" srcset="./assets/convergence_masked_accuracy_FAT5.png" width="100%"> | |
<img alt="Convergence masked accuracy FAT5" src="./assets/convergence_masked_accuracy_FAT5.png" width="100%"> | |
</picture> | |
</p> | |
<p><br></p> | |
<p class="width_125"> | |
Ce phénomène s'observe aussi dans les résultats des finetunings, le FAT5 match les performances du distilcamembert aux alentours de 800 ou 900K steps (à l'exception de l'entité MISC pour la NER) mais ne fait pas mieux au-delà. Cela est néanmoins encourageant en vue d'un passage à l'échelle puisque les modèles distillés issus de modèles plus importants donnent habituellement de meilleurs résultats que les modèles entraînés de zéro.<br> | |
Notons que cette forme de plateau dans les performances serait à confirmer en effectuant plusieurs exécutions avec des configurations différentes (au niveau de la <i>seed</i> notamment) pour proposer des résultats sous la forme d'un intervalle au lieu d'un résultat unique (pour chaque step évaluée, nous utilisons une <i>seed</i> de 42).<br> | |
Signalons également que ce plafonnement pour la partie encodeur a déjà été observé par d'autres auteurs. On peut par exemple citer le CamemBERT(a) 2.0 <d-cite bibtex-key="antoun2024camembert20smarterfrench"></d-cite> qui a également été entraîné sur la partie en français de CulturaX. Le CamemBERT 2.0 n'apparaît pas plus performant que le CamemBERT 1.0 alors qu'il a vu davantage de <i>tokens</i>. Les auteurs obtiennent par contre des gains de performances avec le CamemBERTa 2.0, suggérant ainsi que pour les encodeurs, l'importance est de se focaliser en priorité sur l'architecture (le CamemBERTa 2.0 est un DeBERTaV3 <d-cite bibtex-key="he2023debertav3improvingdebertausing"></d-cite> alors que le CamemBERT 2.0 est un RoBERTa <d-cite bibtex-key="liu2019robertarobustlyoptimizedbert"></d-cite>) plutôt que sur les données. Ce résultat nous invite à réfléchir à une actualisation de l'encodeur du T5.<br> | |
Une dernière observation pouvant être faite, est que si les performances plafonnent, il est possible de se permettre de stopper le pré-entraînement plus tôt et donc réduire les coûts.<br> | |
Dans le tableau ci-dessous, nous listons des estimations de coûts (en euros) pour le pré-entraînement de notre modèle selon divers <i>cloud providers</i>. | |
Pour chacun d'eux, nous nous basons sur le prix horaire d'une A 100 80GB proposé le 20 décembre 2024.<br> | |
Nous indiquons deux cas, un pré-entraînement sur 262 Mds de <i>tokens</i> (seuil où on observe que les performances sur les tâches de classifications commencent à plafonner et que les gains marginaux deviennent alors faibles) sur 419 Mds de <i>tokens</i> (le nombre de <i>tokens</i> vus au maximum par le CamemBERT). | |
<br> | |
</p> | |
<table class="width_125"> | |
<thead> | |
<tr> | |
<th>Cloud provider</th> | |
<th>Prix horaire d'une A 100</th> | |
<th>Prix pour 262 Mds de tokens</th> | |
<th>Prix pour 419 Mds de tokens</th> | |
<th>Note</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>AWS</td> | |
<td>1,77</td> | |
<td>1 616</td> | |
<td>2 586</td> | |
<td></td> | |
</tr> | |
<tr> | |
<td>OVH</td> | |
<td>2,75</td> | |
<td>2 475</td> | |
<td>3 960</td> | |
<td>En optant pour un payement mensuel plutôt qu'horaire, le prix dans les deux cas n'est plus que de 2 200€.</td> | |
</tr> | |
<tr> | |
<td>Azure</td> | |
<td>3,31</td> | |
<td>3 021</td> | |
<td>4 833</td> | |
<td>Le prix horaire a été calculé à partir du prix mensuel de 8 A100.</td> | |
</tr> | |
<tr> | |
<td>Google Cloud</td> | |
<td>3,52</td> | |
<td>3 214</td> | |
<td>5 143</td> | |
<td></td> | |
</tr> | |
</tbody> | |
</table> | |
<br><br> | |
<p><br><br></p> | |
<h4>Temps et émissions par pré-entraînement</h4> | |
<p class="width_125">Les émissions carbones ont été estimées à l’aide du <a class="link" href="https://mlco2.github.io/impact#compute">Machine Learning Impact calculator</a> <d-cite bibtex-key="lacoste2019quantifying"></d-cite>.<br> | |
Notre modèle a été pré-entraîné sur une A100 PCIe 80GB, sur une infrastructure privée. | |
Pour l'efficacité carbone, nous nous sommes basés sur les chiffres journaliers indiqués | |
par <a class="link" href="https://app.electricitymaps.com/zone/FR">electricitymaps</a> pour la France lors de la période de notre pré-entraînement.<br> | |
Les finetunings ont été effectués pour leur part sur une A100 PCIe 40GB. | |
Le temps d’exécution se comptant généralement en heures voire en minutes, pour l’efficacité carbone nous nous référons alors au chiffre d’electricitymaps indiqué pour l’heure en question plutôt que pour le chiffre journalier.<br> | |
Nous estimons ainsi les émissions de notre modèle à 14,084 kg eq. CO2, | |
dont 13,5 kg eq. CO2 pour le pré-entraînement et 0,584 kg eq. CO2 pour les 49 finetunings.<br> | |
À ce chiffre, nous devons ajouter des émissions supplémentaires estimées à 6,24 kg eq. CO2. | |
Elles correspondent au finetuning de modèles pour établir les baselines auxquelles se comparer (0,475 kg eq. CO2), à nos travaux préliminaires en bf16 mixed (4,735 kg eq. CO2 pour le pré-entraînement de trois modèles différents sur 300K steps) et à des tests en bf16 full avant l'entraînement de notre modèle final (1,03 kg eq. en pré-entraînement d'un modèle deux fois plus petit sur 400K steps).<br> | |
Ainsi, au total, nous estimons l’empreinte carbone de nos travaux à 20,324 kg eq. CO2. </p> | |
<p class="width_125">Sur la phase de pré-entraînement (nous n’avons pas assez d’informations pour faire des estimations pour les autres phases), | |
il est alors possible de nous situer vis-à-vis des autres modèles en français pré-entraînés listés précédemment : </p> | |
<table class="width_125"> | |
<thead> | |
<tr> | |
<th>Modèle</th> | |
<th>Temps (H)</th> | |
<th>Emissions (kg Co2 eq)</th> | |
<th>Note</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>Camembert</td> | |
<td>6 144</td> | |
<td>106,91 ✝</td> | |
<td>24H × 256 Tesla V100-SXM2-32GB à 58g (moyenne sur 2019) <br>Les auteurs ne précisent pas les chiffres pour la version large</td> | |
</tr> | |
<tr> | |
<td>Flaubert base <d-cite bibtex-key="le2020flaubert"></d-cite></td> | |
<td>13 120</td> | |
<td>190,24 à 228,29 ✝</td> | |
<td>410H × 32 V100 à 58g (moyenne sur 2019) <br>Le type de la V100 n’est pas spécifié<br>(V100-SXM2-32GB ? V100-SXM2-16GB ? V100-PCIE-16GB ?)</td> | |
</tr> | |
<tr> | |
<td>Flaubert large <d-cite bibtex-key="le2020flaubert"></d-cite></td> | |
<td>49 920</td> | |
<td>723,84 à 868,61 ✝</td> | |
<td>390H × 128 V100 à 58g (moyenne sur 2019) <br>Le type de la V100 n’est pas spécifié<br>(V100-SXM2-32GB ? V100-SXM2-16GB ? V100-PCIE-16GB ?)</td> | |
</tr> | |
<tr> | |
<td>Barthez</td> | |
<td>7 680 ★</td> | |
<td>107,52 à 129,02 ✝</td> | |
<td>60H × 128 V100 à 56g (moyenne sur 2020) <br>Le type de la V100 n’est pas spécifié<br>(V100-SXM2-32GB ? V100-SXM2-16GB ? V100-PCIE-16GB ?)</td> | |
</tr> | |
<tr> | |
<td>FAT5-small</td> | |
<td>1 461</td> | |
<td>13,5</td> | |
<td>1 461H × 1 A100 à 36,96 g (moyenne entre le 18/10/2024 et le 19/12/2024)</td> | |
</tr> | |
</tbody> | |
</table> | |
<p class="width_125">✝ les chiffres indiqués sont des estimations à partir des informations fournies par les auteurs dans leur publication<br> | |
★ nous indiquons uniquement les heures pour le pré-entraînement en français appliqué par dessus le pré-entraînement en anglais initial sur lequel se base le modèle</p> | |
<p><br></p> | |
<h3 id="mod-les-en-anglais">Modèles dans d'autres langues</h3> | |
<p class="width_125"> | |
Notre contribution se focalise sur le français avec l'introduction d'un nouveau modèle. Pour d'autres langues, nous ne pouvons pas nous permettre d’effectuer un travail de la même envergure.<br> | |
Néanmoins, nous mettons à disposition un <a class="link" href="https://github.com/catie-aq/flashT5/blob/main/convert_huggingface_t5.py">code</a> permettant d'adapter vers notre méthode des poids de (m)T5/FLAN-T5 <d-cite bibtex-key="chung2022scaling"></d-cite> déjà pré-entraînés. Nous espérons ainsi que les utilisateurs pourront poursuivre efficacement le pré-entraînement d'un de ces modèles pour l'adapter à des données plus récentes par exemple.<br> | |
Notez cependant que cette adaptation est limitée puisque le pré-entraînement supplémentaire devra être effectué dans la précision du modèle original. Par exemple, si les poids du modèle sont en fp32 (ce qui est le cas du FLAN-T5), l'entraînement ne sera pas aussi rapide que le FAT5 qui est en bf16.<br><br> | |
Pour les anglophones, nous avons déjà adapté les poids des différentes versions du FLAN-T5 à notre méthode. Tous les poids peuvent être trouvés dans cette | |
<a class="link" href="https://huggingface.co/collections/CATIE-AQ/catie-english-fat5-flan-662b679a8e855c7c0137d69e">collection Hugging Face</a>.<br><br> | |
Si vous souhaitez pré-entraîner votre propre modèle (pour être spécialisé dans un domaine spécifique par exemple, et ainsi bénéficier d'un <i>tokenizer</i> personnalisé), nous vous renvoyons à nouveau vers le <a class="link" href="https://github.com/catie-aq/flashT5/tree/main/examples/minipile">tutoriel</a> indiquant comment procéder pour pré-entraîner un modèle sur minipile. Notez que nous avons testé et entraîné le modèle du tutoriel sur une A100, cela peut ou non fonctionner avec d'autres GPU.</p> | |
<p class="width_125"><br><br><br></p> | |
<h2 id="la-suite">La suite</h2> | |
<p class="width_125">Terminons cet article en évoquant ce que nous comptons, ou du moins aimerions, donner comme suite à ce travail.<br></p> | |
<h3>À court terme</h3> | |
<p class="width_125">Il s'agit ici de choses qui auraient déjà dû être présentes dans cet article mais qui ont pris plus de temps que prévu. | |
Typiquement, nous avons terminé la conception des jeux de données mais n'avons pas eu le temps d'effectuer les finetunings.<br> | |
L'objectif est d'effectuer ces tâches prochainement pour pouvoir ajouter les résultats obtenus dans une actualisation de cet article de blog. | |
</p> | |
<h4>Corriger le tokenizer</h4> | |
<p class="width_125"> | |
Le FAT5 actuel est utilisable. Néanmoins, du fait des problèmes avec le <i>tokenizer</i> entraînant des post-traîtement inélégant pour certaines tâches, nous n'excluons pas de ré-entraîner un modèle (sur 1M de steps seulement) avec un nouveau <i>tokenizer</i> permettant un usage plus simple du modèle. | |
<br><br></p> | |
<h4>Modèle instruct</h4> | |
<p class="width_125">Nous souhaiterions tester les capacités de génération de textes du FAT5 de façon plus optimale via notamment l'usage de <i>prompts</i> en développant un modèle instruct.<br> | |
Pour cela, nous disposons du <a class="link" href="https://huggingface.co/datasets/CATIE-AQ/DFP">DFP</a> (<i>Dataset of French Prompts</i>) <d-cite bibtex-key="centre_aquitain_des_technologies_de_l'information_et_electroniques_2023"></d-cite>, un jeu de données de plus de 100M de lignes portant sur trente tâches de NLP. Il reprend la méthodologie du jeu de données <a class="link" href="https://huggingface.co/datasets/bigscience/xP3">xP3</a> ayant servi au mT0 <d-cite bibtex-key="muennighoff2023crosslingualgeneralizationmultitaskfinetuning"></d-cite>. Nous pourrions à cette occasion également vérifier le "Finding 2" de BigScience <d-cite bibtex-key="wang2022languagemodelarchitecturepretraining"></d-cite> (page 9 de la publication) indiquant que les modèles encodeur-décodeur auraient de meilleures capacités en 0-shot que les modèles décodeur. <br> | |
Au-delà du NLP, nous possédons aussi plus de 2M de lignes de prompt de type "open QA" qui doivent nous permettre de tester le FAT5 sur des tâches/connaissances plus généralistes.<br><br> | |
La conception de ce modèle instruct doit en outre nous permettre de travailler sur son alignement, notamment via un jeu de données de 12M de lignes pour effectuer de la DPO en français.<br><br><br></p> | |
<h4>Longues séquences</h4> | |
<p class="width_125"> | |
Le pré-entraînement effectué porte sur des séquences de 1 024 <i>tokens</i>. Or, le noyau CUDA que nous avons développé prend en compte des encodages positionnels permettant d'étendre fortement cette taille de contexte ainsi qu'une inférence linéaire.<br> | |
Dans cette logique, nous avons créé deux jeux de données de longues séquences en français (un de QA, un de résumé de textes) sur lesquels nous souhaitons finetuner notre modèle.<br><br></p> | |
<h3>À long terme</h3> | |
<p class="width_125">Les éléments listés ci-dessous portent sur des pistes à plus long terme. C'est-à-dire que leur implémentation prendra du temps et feront l'objet d'un nouvel article de blog le cas échéant.</p> | |
<h4 id="calcul-lin-aire">Réduction de la mémoire</h4> | |
<p class="width_125">Bien que déjà satisfaits par les optimisations effectuées sur la mémoire via notre noyau CUDA, nous pensons que nous pouvons pousser ces résultats plus loin via d'autres techniques. Par exemple, nous pouvons citer la méthode CCE (Cut Cross-Entropy) <d-cite bibtex-key="wijmans2024cut"></d-cite> avec laquelle nous avons déjà obtenus des résultats intéressants sur des modèles décodeur.<br> | |
De plus, alors que nous nous sommes concentrés sur le pré-entraînement, un travail serait à faire sur l'inférence qui en pratique consomme le plus de ressources dans le temps une fois le modèle en production. Nous pensons notamment à utiliser la SageAttention2 <d-cite bibtex-key="zhang2024sageattention2efficientattentionthorough"></d-cite> sortie pendant que notre modèle s'entraînait. | |
<br><br></p> | |
<h4 id="calcul-lin-aire">Calcul linéaire</h4> | |
<p class="width_125">Dans ce travail, nous présentons un modèle à mémoire linéaire. | |
Une amélioration supplémentaire consisterait à ce qu’en plus de cette mémoire, le modèle opère avec des calculs linéaires.<br> | |
L’idée est de substituer l’attention quadratique traditionnelle par une autre forme d’attention.<br> | |
On peut penser à certaines déjà appliquées au T5, comme celle du LongT5 <d-cite bibtex-key="guo2022longt5"></d-cite>. | |
Il est aussi envisageable de tester des formes plus récentes comme Based <d-cite bibtex-key="arora2024simple"></d-cite>. | |
Nous sommes également intéressés par effectuer des tests avec Hedgehog <d-cite bibtex-key="zhang2024hedgehog"></d-cite>. | |
En effet, il est possible de les associer aux noyaux optimisés disponibles dans <a class="link" href="https://github.com/HazyResearch/ThunderKittens/tree/main/kernels">ThunderKittens</a> <d-cite bibtex-key="thunderkittens"></d-cite>. | |
L’intérêt est qu’il est alors possible de garder le modèle pré-entraîné et, via un finetuning supplémentaire, de remplacer l’attention standard avec softmax par une linéaire avec Hedgehog.<br> | |
LoLCATs <d-cite bibtex-key="zhang2024lolcatslowranklinearizinglarge"></d-cite> effectue ce finetuning via LoRA <d-cite bibtex-key="hu2021loralowrankadaptationlarge"></d-cite>. | |
<br><br></p> | |
<h4 id="passage-l-chelle">Taille des modèles</h4> | |
<p class="width_125"> Des T5/FLAN-T5 ont été entraînés jusqu'à 11 milliards de paramètres, montrant ainsi que cette architecture peut passer à l'échelle.<br> | |
Nous aimerions ainsi proposer des modèles de taille plus importante avec un FAT5-base et un FAT5-large de respectivement 305M et 973M de paramètres que nous souhaiterions ensuite distiller. L'objectif est de proposer des modèles consommant le moins possible en routine/inférence.<br> | |
Nous nous attendons également à ce que les modèles distillés donnent de meilleures performances que des modèles de taille équivalente entraînés de zéro.<br> | |
Cela doit nous permettre également de proposer des modèles qui seront utilisés en pratique. En effet, en l'état actuel pour le français, si l'utilisateur est davantage motivé par les performances plutôt que par la taille mémoire du modèle, il a davantage intérêt à utiliser un CamemBERTa 2.0 pour les tâches de classification. Le présent FAT5 doit ainsi davantage être vue comme une preuve de concept avant un passage à l'échelle qui doit le rendre compétitif. | |
<br><br></p> | |
<h4 id="modeles-specialises">Les données d'entraînement</h4> | |
<p class="width_125"> | |
Dans le cadre de ce travail, nous avons utilisé des données en français « générique » principalement issues de CulturaX. Pendant l'entraînement de notre modèle, | |
Hugging Face a introduit le jeu de données FineWeb2 <d-cite bibtex-key="penedo2024fineweb-2"></d-cite> qui comporte du français. Nous aimerions pré-entraîné un nouveau modèle pour pouvoir comparer l'impact des données de pré-entraînement sur les performances des tâches en aval.<br> | |
Au-delà du français générique, nous souhaitons surtout pouvoir appliquer notre méthodologie à des domaines spécifiques (médecine, variante régionale du français, etc.).<br> | |
Pour cela, il faudrait entraîner un nouveau <em>tokenizer</em> dédié et effectuer un nouveau pré-entraînement pour chacun des domaines choisis. | |
L’intérêt des optimisations mises en place et présentées dans cet article de blog est de permettre une réduction importante du coût du pré-entraînement.<br> | |
Nous souhaiterions ensuite mener une comparaison entre ces petits modèles spécialisés vs. de grands modèles génériques.<br><br></p> | |
<h4 id="modeles-specialises">Une actualisation de l'architecture du T5</h4> | |
<p class="width_125">La dernière piste que nous souhaiterions explorer est une actualisation de l'architecture du T5. En effet, les encodeurs-décodeurs ayant été délaissés, ils n'ont pas bénéficié des améliorations qu'ont reçues ces derniers mois les modèles décodeurs (couches d'activation ou de normalisation plus récentes, <i>multi token prediction</i> <d-cite bibtex-key="gloeckle2024betterfasterlarge"></d-cite>, etc.).</p> | |
<p class="width_125"><br><br><br></p> | |
<h2 id="conclusion">Conclusion</h2> | |
<p class="width_125"> | |
Nous avons introduit le modèle FAT5 (Flash Attention T5) en détaillant notre démarche d’optimisation de différents éléments des processus de pré-entraînement et de finetuning. | |
Celui-ci se base sur des noyaux permettant d'utiliser la Flash Attention avec un T5 et de donner une mémoire linéaire au modèle. | |
Nous avons notamment appliqué nos travaux au français en guise de preuve de concept et fait en sorte qu’il soit aussi utilisable dans n'importe quelle autre langue. | |
Nous espérons que notre méthode, permettant de pré-entraîner de zéro un modèle de 147M de paramètres pour 1 600€, pourra être utile aux personnes disposant de ressources de calculs limitées. | |
Elle ouvre également une voie vers un retour à un usage de modèles encodeur-décodeur plutôt qu’uniquement décodeur.<br> | |
<p class="width_125"><br><br></p> | |
<style> | |
d-appendix .citation { | |
font-size: 11px; | |
line-height: 15px; | |
border-left: 1px solid rgba(0, 0, 0, 0.1); | |
padding-left: 10px; | |
border: 1px solid rgba(0,0,0,0.1); | |
background: #0D1117; | |
padding: 10px 10px; | |
border-radius: 3px; | |
color: rgba(150, 150, 150, 1); | |
overflow: hidden; | |
margin-top: -12px; | |
white-space: pre-wrap; | |
word-wrap: break-word; | |
} | |
</style> | |
<h3 id="citation">Citation</h3> | |
<pre class="citation long">@misc {FAT5, | |
title = { FAT5: Flash Attention T5 }, | |
author = { Boris ALBAR and Loïck BOURDOIS }, | |
organization = { Centre Aquitain des Technologies de l'Information et Electroniques }, | |
year = 2025, | |
url = { https://huggingface.co/spaces/CATIE-AQ/FAT5-report }, | |
doi = { 10.57967/hf/4160 }, | |
publisher = { Hugging Face } | |
}</pre> | |
<d-appendix style="color: #9CA3AF;" > | |
<d-bibliography src="bibliography.bib"></d-bibliography> | |
</d-appendix> | |
</d-article> | |
<script> | |
const article = document.querySelector('d-article'); | |
const toc = document.querySelector('d-contents'); | |
if (toc) { | |
const headings = article.querySelectorAll('h2, h3, h4'); | |
let ToC = `<nav role="navigation" class="l-text figcaption" style="color: #9CA3AF;"><h3>Table des matières</h3>`; | |
let prevLevel = 0; | |
for (const el of headings) { | |
// should element be included in TOC? | |
const isInTitle = el.parentElement.tagName == 'D-TITLE'; | |
const isException = el.getAttribute('no-toc'); | |
if (isInTitle || isException) continue; | |
el.setAttribute('id', el.textContent.toLowerCase().replaceAll(" ", "_")) | |
const link = '<a target="_self" href="' + '#' + el.getAttribute('id') + '">' + el.textContent + '</a>'; | |
const level = el.tagName === 'H2' ? 0 : (el.tagName === 'H3' ? 1 : 2); | |
while (prevLevel < level) { | |
ToC += '<ul>' | |
prevLevel++; | |
} | |
while (prevLevel > level) { | |
ToC += '</ul>' | |
prevLevel--; | |
} | |
if (level === 0) | |
ToC += '<div>' + link + '</div>'; | |
else | |
ToC += '<li>' + link + '</li>'; | |
} | |
while (prevLevel > 0) { | |
ToC += '</ul>' | |
prevLevel--; | |
} | |
ToC += '</nav>'; | |
toc.innerHTML = ToC; | |
toc.setAttribute('prerendered', 'true'); | |
const toc_links = document.querySelectorAll('d-contents > nav a'); | |
window.addEventListener('scroll', (_event) => { | |
if (typeof (headings) != 'undefined' && headings != null && typeof (toc_links) != 'undefined' && toc_links != null) { | |
// Then iterate forwards, on the first match highlight it and break | |
find_active: { | |
for (let i = headings.length - 1; i >= 0; i--) { | |
if (headings[i].getBoundingClientRect().top - 50 <= 0) { | |
if (!toc_links[i].classList.contains("active")) { | |
toc_links.forEach((link, _index) => { | |
link.classList.remove("active"); | |
}); | |
toc_links[i].classList.add('active'); | |
} | |
break find_active; | |
} | |
} | |
toc_links.forEach((link, _index) => { | |
link.classList.remove("active"); | |
}); | |
} | |
} | |
}); | |
} | |
</script> | |
</body> | |
</html> |