pip install deepspeed
DeepSpeed 需要编译 CUDA C++ 代码,因此在构建需要 CUDA 的 PyTorch 扩展时可能会出现错误。这些错误取决于你的系统上如何安装 CUDA。本节主要关注 PyTorch 使用 CUDA 10.2 的情况。
对于其他安装问题,请直接在 DeepSpeed GitHub 仓库上提交问题。
不同版本的 CUDA 工具包¶
PyTorch 自带 CUDA 工具包,但要使用 DeepSpeed 和 PyTorch,你需要在系统范围内安装相同版本的 CUDA。例如,如果你在 Python 环境中安装了 cudatoolkit==10.2,那么你也需要在系统范围内安装 CUDA 10.2。如果你没有安装系统范围的 CUDA,应该先安装。
CUDA 的安装路径可能因系统而异,但常见的路径是 usr/local/cuda-10.2。当 CUDA 正确设置并添加到 PATH 环境变量后,你可以使用以下命令找到安装路径:
which nvcc
多个 CUDA 工具包¶
你可能在系统范围内安装了多个 CUDA 工具包。
通常,包安装程序会设置为最新安装的版本的路径。如果包构建失败,因为它找不到正确的 CUDA 版本(尽管它已经在系统范围内安装),你需要配置 PATH 和 LD_LIBRARY_PATH 环境变量,指向正确的路径。
首先查看这些环境变量的内容:
echo $PATH
echo $LD_LIBRARY_PATH
PATH 列出可执行文件的位置,而 LD_LIBRARY_PATH 列出查找共享库的位置。较早的条目优先于较晚的条目,用 : 分隔多个条目。要告诉构建程序在哪里找到特定的 CUDA 工具包,可以将正确的路径插入到列表首位。此命令是前置而不是覆盖现有值。
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 工具包会启用对新编译器的支持。
你还可以安装一个旧版本的编译器(与当前使用的编译器并存,或者它可能已安装但未默认使用且构建系统无法看到它)。要解决此问题,可以创建一个符号链接,使构建系统能够看到旧版本的编译器。
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++
预构建¶
如果你在安装 DeepSpeed 时遇到问题,或在运行时构建 DeepSpeed,你可以尝试在安装之前预构建 DeepSpeed 模块。要为 DeepSpeed 进行本地构建:
git clone https://github.com/microsoft/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.log
要支持 NVMe 卸载,需在构建命令中添加 DS_BUILD_AIO=1 参数,并确保系统范围内安装 libaio-dev 包。
接下来,你需要通过编辑 TORCH_CUDA_ARCH_LIST 变量来指定 GPU 架构(可以在 此页面 查看 NVIDIA GPU 的架构列表)。要检查与你的架构对应的 PyTorch 版本,运行以下命令:
python -c "import torch; print(torch.cuda.get_arch_list())"
要查找特定 GPU 的架构,运行以下命令:
CUDA_VISIBLE_DEVICES=0 python -c "import torch; print(torch.cuda.get_device_capability())"
如果输出为 8, 6,则可以设置 TORCH_CUDA_ARCH_LIST="8.6"。对于具有不同架构的多 GPU,可以列出它们,例如 TORCH_CUDA_ARCH_LIST="6.1;8.6"。
虽然也可以不指定 TORCH_CUDA_ARCH_LIST 让构建程序自动查询 GPU 架构,但它可能与目标机器上的实际 GPU 不匹配,因此最好明确指定正确的架构。
对于具有相同配置的多台机器上的训练,你需要生成一个二进制 wheel:
git clone https://github.com/microsoft/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_wheel
此命令生成一个二进制 wheel 文件,例如 dist/deepspeed-0.3.13+8cd046f-cp38-cp38-linux_x86_64.whl。你可以在此机器或另一台机器上安装此 wheel 文件。
pip install deepspeed-0.3.13+8cd046f-cp38-cp38-linux_x86_64.whl
多 GPU 网络问题调试¶
当使用 DistributedDataParallel 和多个 GPU 进行训练或推理时,如果遇到进程或节点之间的通信问题,可以使用以下脚本诊断网络问题。
wget https://raw.githubusercontent.com/huggingface/transformers/main/scripts/distributed/torch-distributed-gpu-test.py
例如,测试两个 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
或者在创建 TrainingArguments 对象时传递 debug="underflow_overflow"。
如果你使用自定义训练循环或另一个 Trainer,可以使用以下代码:
from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model)
DebugUnderflowOverflow 会在每次正向调用后插入钩子,以测试输入和输出变量以及相应模块的权重。一旦在激活或权重中至少有一个元素检测到 inf 或 nan,程序将断言并打印类似于以下内容的报告(这是使用 google/mt5-small 和 fp16 混合精度捕获的):
报告的第一行提供了问题发生在哪个批次(这里是第一个批次)。
每个报告的帧从声明对应的模块的完全限定入口开始。例如,encoder.block.2.layer.1.layer_norm 表示这是编码器的第二个块的第一层的层归一化。具体的 forward 调用是 T5LayerNorm。
要解决问题,需要查看数开始变大的地方,可能是切换到 fp32 模式,以防止在乘法或加法时溢出。或者暂时关闭 amp,如果它已启用,将原始 forward 放入一个辅助包装器中:
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)
此外,如果你希望自己在代码中实例化调试器,可以调整打印帧的数量(默认为 21):
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 开始编号。
这在你知道程序在某个批次之后开始行为异常时很有帮助。你可以快进到该区域。例如,如果问题在第 150 个批次开始发生,你可以转储批次 149 和 150 的跟踪并比较数值开始偏离的地方。
你还可以指定在某个批次之后停止训练:
debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3], abort_after_batch_num=3)