Transformers documentation
Débogage
Débogage
Le débogage des problèmes d’entraînement distribué relève généralement de l’une de ces catégories : problèmes numériques, échecs de communication, erreurs d’exécution et erreurs de compilation.
Détection du sous-débordement et du débordement
Les sous-débits et les dépassements se produisent lorsque les activations ou les poids atteignent inf ou nan, ou lorsque loss=NaN. Pour les détecter, activez le module DebugUnderflowOverflow dans TrainingArguments.debug(), ou importez-le et ajoutez-le à votre propre boucle d'entraînement.
from transformers import TrainingArguments
args = TrainingArguments(
debug="underflow_overflow",
...
)from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model)DebugUnderflowOverflow insère des hooks dans le modèle afin de tester les variables d’entrée et de sortie ainsi que les poids du modèle correspondants après chaque appel en avant. Lorsqu’un inf ou un nan est détecté dans au moins un élément des activations ou des poids, le module affiche un rapport semblable à celui ci-dessous.
L'exemple ci-dessous concerne l'entraînement en précision mixte fp16 avec google/mt5-small.
Detected inf/nan during batch_number=0
Last 21 forward frames:
abs min abs max metadata
encoder.block.1.layer.1.DenseReluDense.dropout Dropout
0.00e+00 2.57e+02 input[0]
0.00e+00 2.85e+02 output
[...]
encoder.block.2.layer.0 T5LayerSelfAttention
6.78e-04 3.15e+03 input[0]
2.65e-04 3.42e+03 output[0]
None output[1]
2.25e-01 1.00e+04 output[2]
encoder.block.2.layer.1.layer_norm T5LayerNorm
8.69e-02 4.18e-01 weight
2.65e-04 3.42e+03 input[0]
1.79e-06 4.65e+00 output
encoder.block.2.layer.1.DenseReluDense.wi_0 Linear
2.17e-07 4.50e+00 weight
1.79e-06 4.65e+00 input[0]
2.68e-06 3.70e+01 output
encoder.block.2.layer.1.DenseReluDense.wi_1 Linear
8.08e-07 2.66e+01 weight
1.79e-06 4.65e+00 input[0]
1.27e-04 2.37e+02 output
encoder.block.2.layer.1.DenseReluDense.dropout Dropout
0.00e+00 8.76e+03 input[0]
0.00e+00 9.74e+03 output
encoder.block.2.layer.1.DenseReluDense.wo Linear
1.01e-06 6.44e+00 weight
0.00e+00 9.74e+03 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.DenseReluDense T5DenseGatedGeluDense
1.79e-06 4.65e+00 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.dropout Dropout
3.18e-04 6.27e+04 input[0]
0.00e+00 inf outputLa première ligne indique le numéro du lot où l'erreur s'est produite. Dans ce cas, elle s'est produite sur le lot 0.
Chaque frame décrit le module qu’elle rapporte. Par exemple, la frame ci-dessous rapporte sur encoder.block.2.layer.1.layer_norm, la normalisation de couche de la première couche du deuxième bloc de l’encodeur. Les appels forward sont effectués sur T5LayerNorm.
encoder.block.2.layer.1.layer_norm T5LayerNorm 8.69e-02 4.18e-01 weight 2.65e-04 3.42e+03 input[0] 1.79e-06 4.65e+00 output
La dernière ligne signale la fonction Dropout.forward, qui appelle l’attribut dropout à l’intérieur de la classe DenseReluDense. Le dépassement (inf) s’est produit dans le deuxième bloc de l’encodeur, sur le premier lot. L’élément d’entrée le plus grand était 6.27e+04.
encoder.block.2.layer.1.DenseReluDense T5DenseGatedGeluDense
1.79e-06 4.65e+00 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.dropout Dropout
3.18e-04 6.27e+04 input[0]
0.00e+00 inf outputLes activations en sortie de T5DenseGatedGeluDense.forward ont atteint un maximum de 6.27e+04, proche du maximum de fp16, soit 6.4e+04. À l’étape suivante, Dropout renormalise les poids après avoir mis à zéro certains éléments, ce qui pousse le maximum au-delà de 6.4e+04 et provoque le dépassement.
Maintenant que vous savez où se produit l'erreur, examinez le code de modélisation dans modeling_t5.py.
class T5DenseGatedGeluDense(nn.Module):
def __init__(self, config):
super().__init__()
self.wi_0 = nn.Linear(config.d_model, config.d_ff, bias=False)
self.wi_1 = nn.Linear(config.d_model, config.d_ff, bias=False)
self.wo = nn.Linear(config.d_ff, config.d_model, bias=False)
self.dropout = nn.Dropout(config.dropout_rate)
self.gelu_act = ACT2FN["gelu_new"]
def forward(self, hidden_states):
hidden_gelu = self.gelu_act(self.wi_0(hidden_states))
hidden_linear = self.wi_1(hidden_states)
hidden_states = hidden_gelu * hidden_linear
hidden_states = self.dropout(hidden_states)
hidden_states = self.wo(hidden_states)
return hidden_statesUne solution consiste à passer en fp32 quelques étapes avant que les valeurs ne deviennent trop grandes, afin d'éviter les dépassements lors des multiplications ou des sommes. Une autre option est de désactiver temporairement l'entraînement en précision mixte (amp).
import torch
def forward(self, hidden_states):
device_type = hidden_states.device.type
if torch.is_autocast_enabled(device_type):
with torch.amp.autocast(device_type, enabled=False):
return self._forward(hidden_states)
else:
return self._forward(hidden_states)Le rapport ne couvre que les entrées et les sorties des images complètes. Pour analyser les valeurs intermédiaires à l’intérieur de toute fonction forward, ajoutez detect_overflow après chaque appel forward afin de suivre les inf ou nan dans forwarded_states.
from transformers.debug_utils import detect_overflow
class T5LayerFF(nn.Module):
[...]
def forward(self, hidden_states):
forwarded_states = self.layer_norm(hidden_states)
detect_overflow(forwarded_states, "after layer_norm")
forwarded_states = self.DenseReluDense(forwarded_states)
detect_overflow(forwarded_states, "after DenseReluDense")
return hidden_states + self.dropout(forwarded_states)Configurez le nombre d'images affichées par DebugUnderflowOverflow.
from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model, max_frames_to_save=100)Tracé par lot
DebugUnderflowOverflow peut également tracer les valeurs absolues minimales et maximales de chaque lot, avec la détection du sous-débordement et du débordement désactivée. Cela vous aide à localiser l’endroit où les valeurs commencent à diverger dans votre modèle.
L'exemple ci-dessous trace les lots 1 et 3 (les lots sont indexés à partir de zéro).
debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3]) *** Starting batch number=1 ***
abs min abs max metadata
shared Embedding
1.01e-06 7.92e+02 weight
0.00e+00 2.47e+04 input[0]
5.36e-05 7.92e+02 output
[...]
decoder.dropout Dropout
1.60e-07 2.27e+01 input[0]
0.00e+00 2.52e+01 output
decoder T5Stack
not a tensor output
lm_head Linear
1.01e-06 7.92e+02 weight
0.00e+00 1.11e+00 input[0]
6.06e-02 8.39e+01 output
T5ForConditionalGeneration
not a tensor output
*** Starting batch number=3 ***
abs min abs max metadata
shared Embedding
1.01e-06 7.92e+02 weight
0.00e+00 2.78e+04 input[0]
5.36e-05 7.92e+02 output
[...]DebugUnderflowOverflow signale de nombreuses trames, ce qui facilite l’identification de l’endroit où les valeurs divergent. Si vous savez que le problème se situe autour du lot 150, concentrez la trace sur les lots 149 et 150 pour comparer l’endroit où les nombres commencent à différer.
Vous pouvez également arrêter la trace après un numéro de lot spécifique, par exemple le lot 3.
debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3], abort_after_batch_num=3)Communication
L'entraînement distribué nécessite une communication inter-processus et inter-nœuds, ce qui constitue une source d'erreurs courante.
Téléchargez le script ci-dessous pour diagnostiquer les problèmes réseau, puis exécutez-le pour tester la communication entre les GPU. La commande ci-dessous teste deux GPU. Ajustez --nproc_per_node et --nnodes en fonction de votre système.
wget https://raw.githubusercontent.com/huggingface/transformers/main/scripts/distributed/torch-distributed-gpu-test.py python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py
Le script affiche OK si les deux GPU communiquent et allouent la mémoire avec succès. Consultez le script de diagnostic pour plus de détails et une recette pour l’exécuter dans un environnement SLURM.
Définissez NCCL_DEBUG=INFO pour obtenir une sortie de débogage NCCL détaillée.
NCCL_DEBUG=INFO python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py
DeepSpeed
Lorsque vous rencontrez une erreur, vérifiez d'abord si DeepSpeed en est la cause. Réessayez votre configuration sans DeepSpeed, et si l'erreur persiste, signalez le problème. Pour les problèmes sans rapport avec l'intégration Transformers, ouvrez un ticket sur le dépôt DeepSpeed.
Pour les problèmes liés à l'intégration Transformers, incluez les informations suivantes.
Le fichier de configuration complet de DeepSpeed.
Les arguments de ligne de commande pour Trainer ou les TrainingArguments si vous configurez vous-même le Trainer (ne déversez pas l’ensemble des TrainingArguments, qui contient de nombreuses entrées non pertinentes).
Les sorties de ces commandes.
python -c 'import torch; print(f"torch: {torch.__version__}")' python -c 'import transformers; print(f"transformers: {transformers.__version__}")' python -c 'import deepspeed; print(f"deepspeed: {deepspeed.__version__}")'Un lien vers un notebook Google Colab pour reproduire le problème.
Un jeu de données standard ou non personnalisé, ou un exemple existant pour reproduire le problème.
Processus tué au démarrage
Si le processus DeepSpeed est tué lors du lancement sans retour d’exception, le programme a tenté d’allouer plus de mémoire CPU que ce qui est disponible ou autorisé. Le noyau du système d’exploitation met fin au processus dans les deux cas.
Vérifiez que votre fichier de configuration contient offload_optimizer, offload_param ou les deux, configurés pour décharger sur le CPU.
Si vous avez configuré NVMe et ZeRO-3, essayez plutôt de décharger sur le NVMe. Estimez d’abord les besoins en mémoire de votre modèle.
Perte NaN
Une perte NaN survient souvent lorsqu'un modèle est pré-entraîné en bf16 puis utilisé en fp16 (ce cas est particulièrement fréquent avec les modèles entraînés sur TPU). Utilisez fp32 ou bf16 si votre matériel le prend en charge (TPU, GPU Ampere ou plus récents).
fp16 peut également provoquer un dépassement de capacité. Si votre fichier de configuration ressemble à celui ci-dessous, vous pouvez observer des erreurs de dépassement dans les journaux.
{
"fp16": {
"enabled": "auto",
"loss_scale": 0,
"loss_scale_window": 1000,
"initial_scale_power": 16,
"hysteresis": 2,
"min_loss_scale": 1
}
}L’erreur OVERFLOW! ci-dessous signifie que le loss scaler de DeepSpeed n’a pas pu trouver un coefficient de mise à l’échelle pour surmonter le débordement de la perte. Essayez une valeur plus élevée de initial_scale_power (32 fonctionne généralement).
0%| | 0/189 [00:00<?, ?it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 262144, reducing to 262144 1%|▌ | 1/189 [00:00<01:26, 2.17it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 262144, reducing to 131072.0 1%|█▏ [...] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 14%|████████████████▌ | 27/189 [00:14<01:13, 2.21it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 15%|█████████████████▏ | 28/189 [00:14<01:13, 2.18it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 15%|█████████████████▊ | 29/189 [00:15<01:13, 2.18it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 [...]
DeepSpeed CUDA
DeepSpeed compile du code CUDA C++, ce qui est une source courante d’erreurs de compilation pour les extensions PyTorch nécessitant CUDA. Ces erreurs dépendent de la manière dont CUDA est installé sur votre système.
pip install deepspeed
Pour tout autre problème d’installation, ouvrez un ticket auprès de l’équipe DeepSpeed.
Boîtes à outils non identiques
PyTorch est livré avec son propre kit CUDA, mais DeepSpeed exige une version de CUDA identique installée au niveau système. Si vous avez installé PyTorch avec cudatoolkit==10.2 dans votre environnement Python, vous devrez également installer CUDA 10.2 partout.
L'emplacement exact varie selon le système, mais /usr/local/cuda-10.2 est le chemin le plus courant sur les systèmes Unix. Une fois CUDA installé et ajouté à votre PATH, utilisez cette commande pour trouver l'emplacement d'installation.
which nvccPlusieurs boîtes à outils
Votre système peut avoir plusieurs kits d'outils CUDA installés.
/usr/local/cuda-10.2 /usr/local/cuda-11.0
Les installateurs de paquets définissent généralement les chemins vers la dernière version installée. Si la compilation échoue car la bonne version de CUDA n’est pas trouvée, configurez PATH et LD_LIBRARY_PATH pour qu’ils pointent vers le chemin correct.
Vérifiez d'abord ces variables d'environnement.
echo $PATH
echo $LD_LIBRARY_PATHPATH répertorie les emplacements des exécutables. LD_LIBRARY_PATH répertorie les emplacements des bibliothèques partagées. Les entrées antérieures ont la priorité, et : sépare plusieurs entrées. Préfixez le chemin CUDA correct pour lui accorder la priorité.
# adjust the version and full path if needed
export PATH=/usr/local/cuda-10.2/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATHVérifiez également que les répertoires assignés existent. Le sous-répertoire lib64 contient des objets CUDA .so tels que libcudart.so. Vérifiez les noms de fichiers réels et mettez à jour en conséquence.
Anciennes versions
Les anciennes versions de CUDA exigent parfois d’anciennes versions de compilateur. Par exemple, si CUDA requiert gcc-7 mais que votre système ne dispose que de gcc-9, la compilation échouera. Installez le compilateur ancien requis et créez un lien symbolique afin que le système de compilation de CUDA puisse le trouver.
# adjust the path to your system
sudo ln -s /usr/bin/gcc-7 /usr/local/cuda-10.2/bin/gcc
sudo ln -s /usr/bin/g++-7 /usr/local/cuda-10.2/bin/g++Précompilation
Si vous rencontrez toujours des difficultés pour installer DeepSpeed ou le compiler à l'exécution, précompilez d'abord les modules DeepSpeed. Exécutez les commandes ci-dessous pour une compilation locale.
git clone https://github.com/deepspeedai/DeepSpeed/
cd DeepSpeed
rm -rf build
TORCH_CUDA_ARCH_LIST="8.6" DS_BUILD_CPU_ADAM=1 DS_BUILD_UTILS=1 pip install . \
--global-option="build_ext" --global-option="-j8" --no-cache -v \
--disable-pip-version-check 2>&1 | tee build.logAjoutez
DS_BUILD_AIO=1à la commande de compilation pour utiliser le déchargement NVMe. Assurez-vous d’installer le paquet libaio-dev au niveau système.
Ensuite, définissez votre architecture GPU dans TORCH_CUDA_ARCH_LIST. La liste complète des GPU NVIDIA et de leurs architectures se trouve sur la page CUDA GPUs. Pour vérifier la version de PyTorch correspondant à votre architecture, exécutez la commande ci-dessous.
python -c "import torch; print(torch.cuda.get_arch_list())"Trouvez l'architecture d'un GPU avec la commande suivante.
CUDA_VISIBLE_DEVICES=0 python -c "import torch; print(torch.cuda.get_device_capability())"Exécutez la commande suivante pour trouver l'architecture du GPU 0. La sortie affiche les valeurs major et minor qui, ensemble, forment l'architecture du GPU. L'exemple ci-dessous montre l'architecture 8.6.
CUDA_VISIBLE_DEVICES=0 python -c "import torch; \
print(torch.cuda.get_device_properties(torch.device('cuda')))
"_CudaDeviceProperties(name='GeForce RTX 3090', major=8, minor=6, total_memory=24268MB, multi_processor_count=82)"Pour un résultat de 8, 6, définissez TORCH_CUDA_ARCH_LIST="8.6". Pour plusieurs GPU avec des architectures différentes, listez-les comme TORCH_CUDA_ARCH_LIST="6.1;8.6".
Vous pouvez omettre TORCH_CUDA_ARCH_LIST et laisser le programme de compilation détecter automatiquement l’architecture du GPU, mais cela ne correspond pas nécessairement au GPU réel de la machine cible. Définir explicitement l’architecture est plus fiable.
Pour l'entraînement sur plusieurs machines avec la même configuration, construisez une roue binaire.
git clone https://github.com/deepspeedai/DeepSpeed/
cd DeepSpeed
rm -rf build
TORCH_CUDA_ARCH_LIST="8.6" DS_BUILD_CPU_ADAM=1 DS_BUILD_UTILS=1 \
python setup.py build_ext -j8 bdist_wheelCela génère une roue binaire comme dist/deepspeed-0.3.13+8cd046f-cp38-cp38-linux_x86_64.whl. Installez-la localement ou sur une autre machine.
pip install deepspeed-0.3.13+8cd046f-cp38-cp38-linux_x86_64.whl