Spaces:
Running
Running
import streamlit as st | |
st.set_page_config(layout="wide") | |
from streamlit_extras.switch_page_button import switch_page | |
st.markdown(""" | |
## Dépanner l'inférence | |
### Le modèle est très lent | |
##### Changer la taille de batch | |
Si vous souhaitez une reproductibilité absolue (pour un matériel spécifique et une instruction d'évaluation spécifique), vous utiliserez probablement une taille de batch de 1. Toutefois, si vous augmentez la taille de batch, votre évaluation sera probablement plus rapide (à condition qu'elle soit compatible avec les capacités mémoires de votre matériel).""", unsafe_allow_html=True) | |
st.markdown(""" """) | |
st.markdown(""" | |
##### Parallélisme des données | |
Vous pouvez également dupliquer votre modèle sur plusieurs GPU au lieu de le charger sur un seul, fournir des sous-ensembles de données à chaque GPU, puis agréger les résultats des calculs. | |
Cela signifie que chaque flux de données sera traité en parallèle, en même temps que les autres, ce qui divise le temps d'exécution total par le nombre de GPU utilisé. | |
Notez que si vous le pouvez, tous les GPU doivent se trouver sur un seul nœud afin d'éviter les goulets d'étranglement entre les nœuds. | |
""", unsafe_allow_html=True) | |
st.markdown(""" """) | |
st.markdown(""" | |
##### Changer le code d'inférence | |
Toutes les bibliothèques d'inférence ne fonctionnent pas à la même vitesse, et certains codes sont plus optimisés que d'autres. Vous devrez expérimenter un peu pour trouver quelles bibliothèques ont l'inférence la plus rapide, et si vous utilisez PyTorch, je vous recommande de regarder la *checklist* portant sur l'optimisation de l'inférence dans la [documentation officielle](https://pytorch.org/serve/performance_checklist.html). | |
""", unsafe_allow_html=True) | |
st.markdown(""" """) | |
st.markdown(""" | |
##### Changer la précision | |
Si votre modèle est très lent, vous pouvez réduire sa taille en réduisant la précision des calculs. Un modèle stocké en `float32` effectue des calculs très précis (utilisant 32 bits par nombre stocké) qui sont également très lourds en mémoire et pour les calculs. Passer à du `blfoat16` ou `float16` (moitié de la précision) devrait rendre le modèle deux fois plus rapide avec une perte de précision qui ne devrait presque pas avoir d'importance. Si vous voulez gagner en vitesse, vous pouvez quantifier encore davantage, à 8 ou 4 bits (en utilisant [gptq](https://github.com/AutoGPTQ/AutoGPTQ) ou [bitsandbytes](https://github.com/bitsandbytes-foundation/bitsandbytes) par exemple). Les calculs de matrices à *n* bits devraient être plus rapides et votre modèle prendra encore moins de place en mémoire (cependant, certaines bibliothèques de quantification peuvent être un peu lentes, alors testez les choses pour vos cas d'utilisation !) | |
""", unsafe_allow_html=True) | |
st.markdown(""" """) | |
st.markdown(""" """) | |
st.markdown(""" """) | |
st.markdown(""" | |
### Le modèle est très grand | |
##### Estimer les besoins en mémoire | |
Vous pouvez estimer la mémoire théorique minimale requise pour charger un modèle donné (et donc le matériel) à l'aide de la **formule suivante** : | |
`<mémoire (en GB)> = <nombre de paramètres (en G)> * <facteur de précision>` | |
Comme on peut stocker 8 bits dans un octet, la mémoire nécessaire est le nombre total de paramètres multiplié par le nombre d'octets nécessaires pour stocker un paramètre. Le facteur de précision est donc de 4 pour `float32`, 2 pour `float16` ou `bfoat16`, 1 pour `8bit` et 0,5 pour les modèles `4bit`. | |
Et c'est tout ! | |
Je recommanderais en fait d'utiliser `<mémoire (en GB)> = <nombre de paramètres (en G)> * (<facteur de précision> * 110%)`, pour être plus sûr, car l'inférence nécessite un peu plus de mémoire que le simple chargement du modèle (vous devrez par exemple charger les batchs). | |
""", unsafe_allow_html=True) | |
st.success("""Depuis la rédaction du guide par Clémentine, Quentin Gallouédec d'Hugging Face a rédigé un article de blog détaillé sur ce [point](https://huggingface.co/blog/train_memory). | |
Vous pouvez aussi utiliser ce [Space](https://huggingface.co/spaces/hf-accelerate/model-memory-usage) de Zach Mueller aussi chez Hugging Face.""") | |
st.markdown(""" """) | |
st.markdown(""" """) | |
st.markdown(""" | |
##### Que faire si votre modèle ne tient pas sur un GPU ? | |
""", unsafe_allow_html=True) | |
st.markdown(""" """) | |
st.markdown(""" | |
###### Quantification | |
La première chose évidente est de jouer sur le `<facteur de précision>` ci-dessus. Par exemple passer de `float32` à du 4 bits réduit les besoins en mémoire par 8 ! | |
Cependant, une précision trop faible peut donner de moins bons résultats. Pour certains modèles, en particulier les modèles de taille moyenne, il est préférable de rester en `float16` ou 8 bits. La quantification semble moins affecter les performances des très grands modèles, probablement à cause de la redondance de l'information. | |
""", unsafe_allow_html=True) | |
st.markdown(""" """) | |
st.markdown("""###### Parallélisme | |
Le parallélisme comprend une série de techniques qui découpent votre modèle en sous-modèles plus petits, afin de charger et d'exécuter chacun de ces sous-modèles sur un GPU différent. Cette méthode nécessite moins de mémoire puisque vous ne chargez jamais le modèle complet en une seule fois, mais elle peut être plus lente. | |
Les deux principaux types de parallélisme sont les suivants : | |
- Le parallélisme de pipeline, où le modèle est divisé au niveau de la couche entière, et les couches sont réparties sur différents GPU. Comme la sortie de la couche 1 est l'entrée de la couche 2, l'exécution est plus lente, car les GPU restent inactifs pendant l'attente, ce que l'on appelle une « bulle » (et les données doivent être transférées d'un GPU à l'autre). La bulle peut être réduite en divisant les entrées en batchs plus petits. Cela est ajouté nativement à PyTorch avec la [lib PiPPy](https://github.com/pytorch/PiPPy), et c'est ce que [accelerate](https://github.com/huggingface/accelerate) utilise sous le capot pour le parallélisme. | |
""", unsafe_allow_html=True) | |
st.image('./assets/parallelism_bubble.png',caption="Illustration de la bulle dans le papier GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism de Huang et al.") | |
st.markdown(""" | |
- Le parallélisme tensoriel, où le modèle est divisé au niveau du calcul matriciel. Cela signifie que les matrices seront divisées en lignes ou en colonnes, et que le résultat total sera agrégé. Cette méthode est incroyablement efficace tant que tous les GPU se trouvent sur le même nœud (pour éviter les goulets d'étranglement du réseau inter-nœuds), mais elle peut être difficile à coder. Vous trouverez des implémentations intéressantes dans la librairie [vllm](https://github.com/vllm-project/vllm). Elle permet des accélérations **incroyables**. | |
""", unsafe_allow_html=True) | |
st.info("""Nous vous conseillons le très bon document portant sur les différents types de parallélisme (y compris le parallélisme de données) disponible [ici](https://hf.co/docs/transformers/v4.15.0/en/parallelism).""") | |
st.success("""Depuis la rédaction du guide par Clémentine, les membres de l'équipe Nanotron a sorti [*The Ultra-Scale Playbook: | |
Training LLMs on GPU Clusters*](https://huggingface.co/spaces/nanotron/ultrascale-playbook) qui est une ressource de très grande qualité sur le sujet du parallélisme.""") | |
st.markdown(""" """) | |
st.markdown(""" | |
###### Déchargement du processeur | |
Le déchargement déplace certaines parties des calculs et des modèles dans le but de réduire l'utilisation de la mémoire du GPU. C'est **considérablement plus lent** que n'importe quelle autre méthode, principalement parce que vous avez besoin de déplacer des données d'un périphérique à l'autre en permanence. | |
Un exemple de cette méthode est [ZeRO-Offload](https://arxiv.org/abs/2101.06840) par DeepSpeed, qui distribue les paramètres entre le CPU et le GPU (en plus d'utiliser d'autres optimisations décrites dans le papier ZeRO-2). Sur le CPU sont transmis les gradients, les états de l'optimiseur et les calculs des paramètres du modèle en `fp32` pendant l'optimisation, tandis que sur le GPU on trouve les paramètres en `fp16` et les passes avant/arrière. Il s'agit de tirer parti de la mémoire utilisée par le CPU et des calculs du GPU tout en minimisant la communication entre les deux. | |
""", unsafe_allow_html=True) | |
st.markdown(""" """) | |
st.markdown(""" | |
##### Le modèle tient sur un GPU mais j'obtiens toujours des erreurs OOM ! | |
Vous avez probablement un problème avec la taille de votre contexte. | |
Il est conseillé | |
1) de tester si votre modèle tient vraiment sur un GPU avec des données d'inférence fictives chargées. Ces données fictives doivent avoir une taille de contexte suffisamment grande (représentative de votre tâche) | |
2) diminuer la taille du batch, ou supprimer la recherche automatique de la taille du batch qui pourrait conduire à une erreur OOM accidentelle, si vous l'avez activée | |
3) plus généralement, s'assurer que les échantillons sont présentés à votre modèle dans l'ordre inverse de la taille du contexte, pour être sûr que votre modèle échouera directement si la taille du contexte est trop grande, et non pas après avoir tourné pendant X heures. | |
""", unsafe_allow_html=True) | |
st.markdown(""" """) | |
st.markdown(""" """) | |
st.markdown(""" """) | |
col1, col2, col3= st.columns(3) | |
with col1: | |
if st.button('Section précédente', use_container_width=True): | |
switch_page("V._DÉPANNAGE") | |
with col2: | |
if st.button("Accueil", use_container_width=True): | |
switch_page("Home") | |
with col3: | |
if st.button("Section suivante", use_container_width=True): | |
switch_page("V.2._Dépanner la reproductibilité") |