Transformers 4.37 中文文档(十)(1)

简介: Transformers 4.37 中文文档(十)


原文:huggingface.co/docs/transformers

调试

原始文本:huggingface.co/docs/transformers/v4.37.2/en/debugging

在多个 GPU 上进行训练可能是一个棘手的任务,无论是遇到安装问题还是 GPU 之间的通信问题。这个调试指南涵盖了一些可能遇到的问题以及如何解决它们。

DeepSpeed CUDA 安装

如果您正在使用 DeepSpeed,您可能已经使用以下命令安装了它。

pip install deepspeed

DeepSpeed 编译 CUDA C++代码,当构建需要 CUDA 的 PyTorch 扩展时,这可能是错误的潜在来源。这些错误取决于 CUDA 在您的系统上的安装方式,本节重点介绍了使用* CUDA 10.2 *构建的 PyTorch。

对于任何其他安装问题,请提出问题给 DeepSpeed 团队。

不同的 CUDA 工具包

PyTorch 自带其自己的 CUDA 工具包,但要使用 DeepSpeed 与 PyTorch,您需要在整个系统中安装相同版本的 CUDA。例如,如果您在 Python 环境中安装了cudatoolkit==10.2的 PyTorch,则您还需要在整个系统中安装 CUDA 10.2。如果您的系统中没有安装 CUDA,则应首先安装它。

确切的位置可能因系统而异,但在许多 Unix 系统上,usr/local/cuda-10.2是最常见的位置。当 CUDA 正确设置并添加到您的PATH环境变量时,您可以使用以下命令找到安装位置:

which nvcc

多个 CUDA 工具包

您的系统中可能安装了多个 CUDA 工具包。

/usr/local/cuda-10.2
/usr/local/cuda-11.0

通常,软件包安装程序会将路径设置为最后安装的版本。如果软件包构建失败,因为找不到正确的 CUDA 版本(尽管它已经在整个系统中安装),则需要配置PATHLD_LIBRARY_PATH环境变量以指向正确的路径。

首先查看这些环境变量的内容:

echo $PATH
echo $LD_LIBRARY_PATH

PATH列出了可执行文件的位置,LD_LIBRARY_PATH列出了共享库的查找位置。较早的条目优先于后续的条目,:用于分隔多个条目。为了告诉构建程序要找到您想要的特定 CUDA 工具包,插入正确的路径以首先列出。此命令在现有值之前而不是覆盖现有值。

# 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_PATH

此外,您还应检查您分配的目录是否实际存在。lib64子目录包含各种 CUDA.so对象(如libcudart.so),虽然您的系统不太可能以不同的名称命名它们,但您应检查实际名称并相应更改。

较旧的 CUDA 版本

有时,较旧的 CUDA 版本可能拒绝与更新的编译器一起构建。例如,如果您有gcc-9但 CUDA 需要gcc-7。通常,安装最新的 CUDA 工具包可以支持更新的编译器。

您还可以安装一个较旧版本的编译器,以及您当前使用的一个(或者可能已经安装,但默认情况下未使用,构建系统无法看到)。要解决此问题,您可以创建一个符号链接,以便构建系统能够看到较旧的编译器。

# adapt 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++

多 GPU 网络问题调试

当使用DistributedDataParallel和多个 GPU 进行训练或推理时,如果遇到进程和/或节点之间的互通问题,您可以使用以下脚本来诊断网络问题。

wget https://raw.githubusercontent.com/huggingface/transformers/main/scripts/distributed/torch-distributed-gpu-test.py

例如,要测试 2 个 GPU 如何交互,请执行:

python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py

如果两个进程都可以相互通信并分配 GPU 内存,每个进程都将打印 OK 状态。

对于更多的 GPU 或节点,请调整脚本中的参数。

您将在诊断脚本中找到更多详细信息,甚至可以了解如何在 SLURM 环境中运行它的方法。

另一个调试级别是添加NCCL_DEBUG=INFO环境变量如下:

NCCL_DEBUG=INFO python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py

这将输出大量与 NCCL 相关的调试信息,如果发现有问题报告,您可以在网上搜索。或者如果您不确定如何解释输出,可以在 Issue 中分享日志文件。

下溢和溢出检测

此功能目前仅适用于 PyTorch。

对于多 GPU 训练,需要 DDP(torch.distributed.launch)。

此功能可与任何基于nn.Module模型一起使用。

如果开始出现loss=NaN或模型由于激活或权重中的infnan而表现出其他异常行为,需要找出第一个下溢或溢出发生的位置以及导致其发生的原因。幸运的是,您可以通过激活一个特殊模块来轻松实现自动检测。

如果您正在使用 Trainer,您只需要添加:

--debug underflow_overflow

除了正常的命令行参数外,在创建 TrainingArguments 对象时,也可以传递debug="underflow_overflow"

如果您正在使用自己的训练循环或另一个 Trainer,可以通过以下方式实现相同的效果:

from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model)

DebugUnderflowOverflow 会在模型中插入钩子,每次前向调用后立即测试输入和输出变量以及相应模块的权重。一旦在激活或权重的至少一个元素中检测到infnan,程序将断言并打印类似于这样的报告(这是在 fp16 混合精度下使用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 output

示例输出已经为简洁起见进行了修剪。

第二列显示了绝对最大元素的值,因此如果您仔细查看最后几个帧,输入和输出的范围在1e4。因此,当此训练在 fp16 混合精度下进行时,最后一步发生了溢出(因为在fp16下,在inf之前的最大数字是64e3)。为了避免在fp16下发生溢出,激活必须保持远低于1e4,因为1e4 * 1e4 = 1e8,因此任何具有大激活的矩阵乘法都将导致数值溢出条件。

在跟踪的最开始,您可以发现问题发生在哪个批次号(这里Detected inf/nan during batch_number=0表示问题发生在第一个批次)。

每个报告的帧都以声明相应模块的完全限定条目开头。如果我们只看这个帧:

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.layer_norm表示它是编码器第二块的第一层的层归一化。而forward的具体调用是T5LayerNorm

让我们看一下报告的最后几个帧:

Detected inf/nan during batch_number=0
Last 21 forward frames:
abs min  abs max  metadata [...]
                  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.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 output

最后一个帧报告了Dropout.forward函数,第一个条目是唯一输入,第二个是唯一输出。您可以看到它是从DenseReluDense类内部的dropout属性调用的。我们可以看到它发生在第二块的第一层,在第一个批次期间。最后,绝对最大的输入元素是6.27e+04,输出也是inf

您可以在这里看到,T5DenseGatedGeluDense.forward的输出激活结果,其绝对最大值约为 62.7K,非常接近 fp16 的 64K 顶限。在下一个帧中,我们有Dropout,它在将一些元素归零后重新归一化权重,将绝对最大值推到超过 64K,导致溢出(inf)。

正如您所看到的,当数字开始变得非常大时,我们需要查看前面的帧以了解情况。

让我们将报告与models/t5/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_states

现在很容易看到dropout调用以及所有先前的调用。

由于检测发生在前向挂钩中,这些报告将在每个forward返回后立即打印。

回到完整报告,要对其进行操作并解决问题,我们需要向上移动几帧,找到数字开始增加的地方,并且很可能在这里切换到fp32模式,以便在乘法或求和时数字不会溢出。当然,可能还有其他解决方案。例如,我们可以在将原始forward移入辅助包装器后,暂时关闭amp,如下所示:

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_states
import torch
def forward(self, hidden_states):
    if torch.is_autocast_enabled():
        with torch.cuda.amp.autocast(enabled=False):
            return self._forward(hidden_states)
    else:
        return self._forward(hidden_states)

由于自动检测器仅报告完整帧的输入和输出,一旦您知道要查找的位置,您可能还想分析任何特定forward函数的中间阶段。在这种情况下,您可以使用detect_overflow辅助函数将检测器注入到您想要的位置,例如:

from 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)

您可以看到我们添加了 2 个这样的内容,现在我们跟踪forwarded_states中是否在中间某处检测到了infnan

实际上,检测器已经报告了这些,因为上面示例中的每个调用都是一个nn.Module,但是假设如果您有一些本地直接计算,这就是您将如何执行的方式。

此外,如果您在自己的代码中实例化调试器,您可以调整从默认值打印的帧数,例如:

from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model, max_frames_to_save=100)

特定批次的绝对最小值和最大值跟踪

相同的调试类可以用于关闭下溢/溢出检测功能的每批次跟踪。

假设您想要观察给定批次的每个forward调用的所有成分的绝对最小值和最大值,并且仅对批次 1 和 3 执行此操作。然后,您可以将此类实例化为:

debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3])

现在,完整的批次 1 和 3 将使用与下溢/溢出检测器相同的格式进行跟踪。

批次是从 0 开始索引的。

如果您知道程序在某个特定批次号之后开始表现不当,那么您可以直接快进到该区域。以下是这种配置的示例截断输出:

*** 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
[...]

在这里,您将获得大量的帧转储 -  与您模型中的前向调用数量一样多,因此可能或可能不是您想要的,但有时它可能比普通调试器更容易用于调试目的。例如,如果问题开始在批次号为 150  时发生。因此,您可以为批次 149 和 150 转储跟踪,并比较数字开始发散的地方。

您还可以指定在哪个批次号之后停止训练,例如:

debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3], abort_after_batch_num=3)


Transformers 4.37 中文文档(十)(2)https://developer.aliyun.com/article/1564903

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
3月前
|
存储 机器学习/深度学习 编解码
Transformers 4.37 中文文档(五)(5)
Transformers 4.37 中文文档(五)
189 0
|
3月前
|
数据可视化 自动驾驶 机器人
Transformers 4.37 中文文档(五)(4)
Transformers 4.37 中文文档(五)
36 0
|
3月前
|
存储 编解码 JSON
Transformers 4.37 中文文档(四)(5)
Transformers 4.37 中文文档(四)
43 1
Transformers 4.37 中文文档(四)(5)
|
3月前
|
自然语言处理 PyTorch TensorFlow
Transformers 4.37 中文文档(一)(1)
Transformers 4.37 中文文档(一)
60 1
|
3月前
|
自然语言处理 PyTorch TensorFlow
Transformers 4.37 中文文档(一百)(4)
Transformers 4.37 中文文档(一百)
37 1
|
3月前
|
PyTorch TensorFlow 调度
Transformers 4.37 中文文档(一)(5)
Transformers 4.37 中文文档(一)
57 1
|
3月前
|
自然语言处理 PyTorch TensorFlow
Transformers 4.37 中文文档(三)(2)
Transformers 4.37 中文文档(三)
27 1
|
3月前
|
API 计算机视觉 Python
Transformers 4.37 中文文档(八)(2)
Transformers 4.37 中文文档(八)
50 2
|
3月前
|
存储 自然语言处理 测试技术
Transformers 4.37 中文文档(八)(4)
Transformers 4.37 中文文档(八)
59 2
|
3月前
|
存储 PyTorch TensorFlow
Transformers 4.37 中文文档(二)(1)
Transformers 4.37 中文文档(二)
63 1