Transformers documentation
Depuración
Depuración
La depuración de problemas en el entrenamiento distribuido suele encajar en una de estas categorías: problemas numéricos, fallos de comunicación, errores de tiempo de ejecución y errores de compilación.
Detección de desbordamiento y subdesbordamiento
El subdesbordamiento y el desbordamiento ocurren cuando las activaciones o los pesos alcanzan inf o nan, o cuando loss=NaN. Para detectar estos casos, habilita el módulo DebugUnderflowOverflow en TrainingArguments.debug(), o impórtalo y añádelo a tu propio bucle de entrenamiento.
from transformers import TrainingArguments
args = TrainingArguments(
debug="underflow_overflow",
...
)from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model)DebugUnderflowOverflow inserta ganchos en el modelo para probar las variables de entrada y salida y los pesos del modelo correspondientes después de cada llamada de propagación hacia adelante. Cuando se detecta inf o nan en al menos un elemento de las activaciones o los pesos, el módulo imprime un informe como el siguiente.
El siguiente ejemplo es para el entrenamiento con precisión mixta fp16 con 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 primera línea muestra el número de lote donde se produjo el error. En este caso, ocurrió en el lote 0.
Cada cuadro describe el módulo sobre el que informa. Por ejemplo, el cuadro siguiente informa sobre encoder.block.2.layer.1.layer_norm, la normalización de capas de la primera capa del segundo bloque del codificador. Las llamadas de propagación hacia adelante son a 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
El último cuadro informa sobre la función Dropout.forward, que llama al atributo dropout dentro de la clase DenseReluDense. El desbordamiento (inf) ocurrió en el segundo bloque del codificador en el primer lote. El elemento de entrada más grande era 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 outputLas activaciones de salida de T5DenseGatedGeluDense.forward alcanzaron un máximo de 6.27e+04, que está cerca del máximo de fp16, 6.4e+04. En el siguiente paso, Dropout renormaliza los pesos después de poner a cero algunos elementos, lo que empuja el máximo por encima de 6.4e+04 y provoca el desbordamiento.
Ahora que sabe dónde se produce el error, investigue el código de modelado en 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_statesUna solución es cambiar a fp32 unos pasos antes de que los valores crecieran demasiado, para que los números no desborden al multiplicarse o sumarse. Otra opción es desactivar temporalmente el entrenamiento con precisión mixta (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)El informe solo cubre las entradas y salidas de los fotogramas completos. Para analizar los valores intermedios dentro de cualquier función forward, añade detect_overflow después de cada llamada a forward para rastrear inf o nan en 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)Configure el número de fotogramas que imprime DebugUnderflowOverflow.
from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model, max_frames_to_save=100)Trazado por lotes
DebugUnderflowOverflow también puede rastrear los valores mínimo y máximo absolutos en cada lote con la detección de desbordamiento y subdesbordamiento desactivada. Esto le ayuda a localizar dónde comienzan a divergir los valores en su modelo.
El ejemplo siguiente traza los lotes 1 y 3 (los lotes se indexan desde cero).
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 informa de muchos marcos, lo que facilita identificar dónde divergen los valores. Si sabes que el problema está alrededor del lote 150, centra el seguimiento en los lotes 149 y 150 para comparar dónde empiezan a diferir los números.
También puede detener la traza después de un número de lote específico, por ejemplo, el lote 3.
debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3], abort_after_batch_num=3)Comunicación
El entrenamiento distribuido requiere comunicación entre procesos y entre nodos, lo cual es una fuente común de errores.
Descargue el siguiente script para diagnosticar problemas de red y, a continuación, ejecute la prueba de comunicación entre GPUs. El siguiente comando prueba dos GPUs. Ajuste --nproc_per_node y --nnodes para su sistema.
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
El script imprime OK si ambas GPUs se comunican y asignan memoria correctamente. Consulte el script de diagnóstico para más detalles y una receta para ejecutarlo en un entorno SLURM.
Establezca NCCL_DEBUG=INFO para obtener una salida detallada de depuración de NCCL.
NCCL_DEBUG=INFO python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py
DeepSpeed
Cuando aparece un error, primero comprueba si DeepSpeed es la causa. Repite la configuración sin DeepSpeed y, si el error persiste, reporta el problema. Para problemas no relacionados con la integración de Transformers, abre un problema en el repositorio de DeepSpeed.
Para los problemas relacionados con la integración de Transformers, incluye la siguiente información.
El archivo de configuración completo de DeepSpeed.
Los argumentos de línea de comandos para Trainer o los TrainingArguments si configuras el Trainer manualmente mediante un script (no incluyas todo el TrainingArguments, ya que contiene muchas entradas irrelevantes).
Las salidas de estos comandos.
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 enlace a un cuaderno de Google Colab para reproducir el problema.
Un conjunto de datos estándar o no personalizado, o un ejemplo existente para reproducir el problema.
Proceso terminado al inicio
Si el proceso de DeepSpeed se interrumpe durante el inicio sin una trazada de error, el programa intentó asignar más memoria de CPU de la disponible o permitida. El kernel del sistema operativo termina el proceso en cualquiera de los dos casos.
Comprueba si tu archivo de configuración tiene offload_optimizer, offload_param o ambos configurados para descargar en la CPU.
Si tienes NVMe y ZeRO-3 configurados, prueba a descargar en el NVMe en su lugar. Estima primero los requisitos de memoria de tu modelo.
Pérdida NaN
La pérdida NaN suele ocurrir cuando un modelo se preentrena en bf16 y luego se utiliza con fp16 (esto es especialmente común con modelos entrenados en TPU). Utilice fp32 o bf16 si su hardware lo admite (TPUs, GPUs Ampere o más recientes).
fp16 también puede causar desbordamiento. Si su archivo de configuración se parece al siguiente, es posible que vea errores de desbordamiento en los registros.
{
"fp16": {
"enabled": "auto",
"loss_scale": 0,
"loss_scale_window": 1000,
"initial_scale_power": 16,
"hysteresis": 2,
"min_loss_scale": 1
}
}El error OVERFLOW! de abajo significa que el escalador de pérdida de DeepSpeed no pudo encontrar un coeficiente de escalado para superar el desbordamiento de la pérdida. Prueba con un valor más alto de initial_scale_power (32 suele funcionar).
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 compila código CUDA C++, lo cual es una causa común de errores de compilación para las extensiones de PyTorch que requieren CUDA. Estos errores dependen de cómo se instala CUDA en tu sistema.
pip install deepspeed
Para cualquier otro problema de instalación, abre un issue con el equipo de DeepSpeed.
Kits de herramientas no idénticos
PyTorch incluye su propio kit de herramientas CUDA, pero DeepSpeed requiere que se instale una versión de CUDA idéntica a nivel de sistema. Si instalaste PyTorch con cudatoolkit==10.2 en tu entorno de Python, también necesitarás tener CUDA 10.2 instalado en todo el sistema.
La ubicación exacta varía según el sistema, pero /usr/local/cuda-10.2 es la ruta más común en sistemas Unix. Una vez que CUDA esté configurado y añadido a tu PATH, encuentra la ubicación de la instalación con este comando.
which nvccVarios kits de herramientas
Es posible que su sistema tenga más de un kit de herramientas de CUDA instalado.
/usr/local/cuda-10.2 /usr/local/cuda-11.0
Los instaladores de paquetes suelen establecer las rutas hacia la última versión instalada. Si la compilación falla porque no puede encontrar la versión correcta de CUDA, configure PATH y LD_LIBRARY_PATH para que apunten a la ruta correcta.
Comprueba primero estas variables de entorno.
echo $PATH
echo $LD_LIBRARY_PATHPATH lista las ubicaciones de los ejecutables. LD_LIBRARY_PATH lista las ubicaciones de las bibliotecas compartidas. Las entradas anteriores tienen prioridad y : separa varias entradas. Añade la ruta correcta de CUDA al principio para darle prioridad.
# 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_PATHVerifique también que los directorios asignados existan. El subdirectorio lib64 contiene objetos CUDA .so como libcudart.so. Compruebe los nombres de archivo reales y actualícelos en consecuencia.
Versiones anteriores
Las versiones anteriores de CUDA a veces requieren versiones anteriores del compilador. Por ejemplo, si CUDA requiere gcc-7 pero su sistema solo tiene gcc-9, la compilación fallará. Instale el compilador anterior necesario y cree un enlace simbólico para que el sistema de compilación de CUDA pueda encontrarlo.
# 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++Precompilar
Si aún tienes problemas para instalar DeepSpeed o para compilarlo en tiempo de ejecución, compila primero los módulos de DeepSpeed. Ejecuta los siguientes comandos para una compilación local.
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.logAgregue
DS_BUILD_AIO=1al comando de compilación para usar el offload de NVMe. Asegúrese de instalar el paquete libaio-dev a nivel de sistema.
A continuación, configure su arquitectura de GPU en TORCH_CUDA_ARCH_LIST. Una lista completa de las GPUs de NVIDIA y sus arquitecturas se encuentra en la página de GPUs de CUDA. Para verificar la versión de PyTorch que corresponde a su arquitectura, ejecute el comando siguiente.
python -c "import torch; print(torch.cuda.get_arch_list())"Encuentra la arquitectura de una GPU con el siguiente comando.
CUDA_VISIBLE_DEVICES=0 python -c "import torch; print(torch.cuda.get_device_capability())"Ejecuta el siguiente comando para encontrar la arquitectura de la GPU 0. La salida muestra los valores major y minor que, juntos, forman la arquitectura de la GPU. El ejemplo siguiente muestra la arquitectura 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)"Para un resultado de 8, 6, establezca TORCH_CUDA_ARCH_LIST="8.6". Para varias GPU con arquitecturas diferentes, enumérelas como TORCH_CUDA_ARCH_LIST="6.1;8.6".
Puede omitir TORCH_CUDA_ARCH_LIST y dejar que el programa de compilación detecte la arquitectura de la GPU automáticamente, pero es posible que no coincida con la GPU real de la máquina de destino. Establecer la arquitectura de forma explícita es más fiable.
Para entrenar en varias máquinas con la misma configuración, construye una rueda binaria.
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_wheelEsto genera una rueda binaria como dist/deepspeed-0.3.13+8cd046f-cp38-cp38-linux_x86_64.whl. Instálala localmente o en otra máquina.
pip install deepspeed-0.3.13+8cd046f-cp38-cp38-linux_x86_64.whl