资讯详情

Transformers 深度调试指南:多 GPU 通信故障定位与数值下溢/溢出检测实战

📅 2026/9/10 2:10:33 | 华诺云谱 👁 阅读
Transformers 深度调试指南:多 GPU 通信故障定位与数值下溢/溢出检测实战
Transformers 深度调试指南多 GPU 通信故障定位与数值下溢/溢出检测实战【免费下载链接】transformers Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers调试分布式训练问题通常可以归为几大类数值问题inf/nan/lossNaN、进程间通信失败、运行时错误和构建错误。本文以 Hugging Face Transformers 官方调试文档docs/source/ko/debugging.md英文版见 docs/source/en/debugging.md为主线结合本仓库的实际源码系统讲解两大核心调试手段多 GPU 通信网络诊断脚本与自动化的下溢/溢出检测模块并补充 DeepSpeed 场景下常见的排错路径。读完本文你将能够用一条命令诊断多卡/多机 NCCL 通信是否正常在训练出现lossNaN时自动定位到产生inf/nan的第一个模块、第一个批次并据此修复针对特定批次跟踪张量绝对值的演变快速锁定数值发散起点。多 GPU 网络通信问题诊断使用DistributedDataParallel进行多 GPU 训练或推理时进程之间、节点之间的相互通信是最容易出错的环节之一。卡住、超时、barrier挂起等表现往往并非模型代码问题而是底层网络通信问题。此时应当先用独立的最小诊断脚本确认GPU 之间能否通信、能否分配显存而不是在庞大训练脚本里大海捞针。诊断脚本与快速上手仓库中内置了官方诊断脚本 scripts/distributed/torch-distributed-gpu-test.py它会在集群单节点或多节点中通过nccl后端检查所有 GPU 能否互相通信并成功分配显存。以测试 2 块 GPU 的交互为例python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py如果两个进程成功通信并分配了 GPU 内存每个进程都会打印OK状态。更多 GPU 或更多节点时只需调整脚本的启动参数--nproc_per_node每个节点上的进程数通常等于每节点 GPU 数--nnodes参与训练的节点总数使用自定义地址/端口时追加--master_addr $MASTER_ADDR --master_port $MASTER_PORT也可以改用 rdzv API--rdzv_endpoint $MASTER_ADDR:$MASTER_PORT --rdzv_backend c10d若 PyTorch 版本低于 1.9请使用torch.distributed.launch替代torch.distributed.run。从源码理解诊断逻辑阅读 scripts/distributed/torch-distributed-gpu-test.py 的源码可以看到它依次完成以下几项关键检查进程绑定 GPU通过环境变量LOCAL_RANK读取本地进程编号并调用torch.cuda.set_device(local_rank)将每个进程绑定到对应的 GPU初始化进程组dist.init_process_group(nccl)建立 NCCL 通信组这一步失败通常意味着网络端口、MASTER_ADDR/MASTER_PORT配置有问题通信验证执行dist.all_reduce(torch.ones(1).to(device), opdist.ReduceOp.SUM)做一次全局求和再调用dist.barrier()同步所有进程——如果程序挂在barrier调用上说明存在网络问题脚本源码注释中对此有明确提示显存分配验证torch.ones(1).cuda(local_rank)触发 CUDA 显存分配检查 GPU 是否可用输出结果每个进程通过printflock打印形如[hostname-local_rank] is OK (global rank: x/y)的信息rank 0 额外打印pttorch 版本, cudaCUDA 版本, ncclNCCL 版本便于核对各节点环境是否一致。值得注意的是脚本中的printflock辅助函数它利用文件锁fcntl.flock保证多进程并发打印时输出不会互相交错这也是分布式诊断输出可读性的细节实现。启用 NCCL 详细日志如果基础诊断失败可以为命令追加NCCL_DEBUGINFO环境变量让 NCCL 输出大量底层调试信息NCCL_DEBUGINFO python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py这会打印出 NCCL 初始化、连接握手、通信通道建立等大量日志。拿到这些日志后你可以自行检索关键词定位问题如果不会解读也可以将日志文件附在 issue 中提交给维护者。需要注意的是NCCL_DEBUGINFO输出量很大建议把日志重定向到文件后再分析。在 SLURM 环境中运行诊断脚本同样适用于 SLURM 调度环境其源码注释中给出了完整的 SLURM 提交脚本配方核心要点如下#SBATCH --job-nametest-nodes # 任务名 #SBATCH --nodes2 # 节点数 #SBATCH --ntasks-per-node1 # 关键每个分布式节点只分配 1 个任务 #SBATCH --cpus-per-task10 # 每个任务的核心数 #SBATCH --gresgpu:4 # 每节点 GPU 数量 #SBATCH --time 0:05:00 # 最大执行时间 (HH:MM:SS) #SBATCH --output%x-%j.out # 输出文件名 GPUS_PER_NODE4 MASTER_ADDR$(scontrol show hostnames $SLURM_JOB_NODELIST | head -n 1) MASTER_PORT6000 srun --jobid $SLURM_JOBID bash -c python -m torch.distributed.run \ --nproc_per_node $GPUS_PER_NODE --nnodes $SLURM_NNODES --node_rank $SLURM_PROCID \ --master_addr $MASTER_ADDR --master_port $MASTER_PORT \ torch-distributed-gpu-test.py其中--ntasks-per-node1是必须的每个节点只启动一个srun任务再由torch.distributed.run在节点内部拉起--nproc_per_node个进程避免任务与进程两层调度互相冲突。数值下溢/溢出检测DebugUnderflowOverflow当训练出现lossNaN或模型因inf/nan表现出其他异常行为时关键在于找出第一次出现下溢/溢出underflow/overflow的位置。激活值或权重到达inf/nan通常意味着计算过程中数值范围失控。Transformers 提供了专门模块DebugUnderflowOverflow自动完成检测避免人工逐步排查。何时使用与使用前提该功能当前仅在 PyTorch 下可用多 GPU 训练需要使用 DDPtorch.distributed.launch/torchrun。实际上 src/transformers/trainer.py 中的实现会在args.n_gpu 1时直接抛出异常提示Currently --debug underflow_overflow is not supported under DP. Please use DDP with torchrun因为nn.DataParallel会复制模型导致注册的 hook 在其他 GPU 上失效该功能适用于基于nn.Module的模型。方式一通过 Trainer 启用使用 [Trainer] 时只需在原有命令行参数中追加--debug underflow_overflow或者在创建 [TrainingArguments] 对象时传入from transformers import TrainingArguments args TrainingArguments( debugunderflow_overflow, ... )该参数的定义见 src/transformers/training_args.pydebug是str或list[DebugOption]默认值为可选项包括underflow_overflow检测模型输入输出的溢出和tpu_metrics_debug打印 TPU 指标。多个选项以空格分隔的字符串传入时会在内部被解析为DebugOption列表见 training_args.py。Trainer 在训练初始化阶段检测到DebugOption.UNDERFLOW_OVERFLOW in args.debug后会自动实例化DebugUnderflowOverflow(self.model)见 trainer.py。方式二在自定义训练循环中启用不使用 Trainer 或使用其他训练框架时可以手动实例化调试器from transformers.debug_utils import DebugUnderflowOverflow debug_overflow DebugUnderflowOverflow(model)工作原理forward hook 与帧缓冲[DebugUnderflowOverflow] 的实现位于 src/transformers/debug_utils.py。从源码看其工作机制是注册 forward hookregister_forward_hook对模型执行self.model.apply(self._register_forward_hook)即递归地为模型内每一个nn.Module注册forward_hook。该 hook 在对应模块的forward返回后立即触发因此报告在每个forward刚结束时就生成逐帧记录每一帧frame记录三部分信息——该模块的完全限定名与类名如encoder.block.2.layer.1.layer_norm T5LayerNorm、模块自身参数的绝对最小/最大值、每个输入/输出张量的绝对最小/最大值非张量输入输出None、元组等会标记为None或not a tensor见analyse_variable与create_framedebug_utils.py帧缓冲内部维护一个collections.deque([], max_frames_to_save)默认保存最近 21 帧max_frames_to_save21一旦检测到溢出就倒序倾倒出问题出现前的最新 21 个帧为定位问题提供上下文检测逻辑对每个张量调用detect_overflow(var, ctx)debug_utils.py通过torch.isnan(var).any()与torch.isinf(var).any()判断是否含nan或inf一旦任一激活或权重元素出现inf/nan程序会抛出ValueError断言中止并打印报告。解读检测报告下面是一份典型的检测报告示例取自 fp16 混合精度下google/mt5-small的训练中间部分为节省篇幅已省略Detected inf/nan during batch_number0 Last 21 forward frames: abs min abs max metadata encoder.block.1.layer.1.DenseReluDense.dropout Dropout 0.00e00 2.57e02 input[0] 0.00e00 2.85e02 output [...] encoder.block.2.layer.0 T5LayerSelfAttention 6.78e-04 3.15e03 input[0] 2.65e-04 3.42e03 output[0] None output[1] 2.25e-01 1.00e04 output[2] encoder.block.2.layer.1.layer_norm T5LayerNorm 8.69e-02 4.18e-01 weight 2.65e-04 3.42e03 input[0] 1.79e-06 4.65e00 output encoder.block.2.layer.1.DenseReluDense.wi_0 Linear 2.17e-07 4.50e00 weight 1.79e-06 4.65e00 input[0] 2.68e-06 3.70e01 output encoder.block.2.layer.1.DenseReluDense.wi_1 Linear 8.08e-07 2.66e01 weight 1.79e-06 4.65e00 input[0] 1.27e-04 2.37e02 output encoder.block.2.layer.1.DenseReluDense.dropout Dropout 0.00e00 8.76e03 input[0] 0.00e00 9.74e03 output encoder.block.2.layer.1.DenseReluDense.wo Linear 1.01e-06 6.44e00 weight 0.00e00 9.74e03 input[0] 3.18e-04 6.27e04 output encoder.block.2.layer.1.DenseReluDense T5DenseGatedGeluDense 1.79e-06 4.65e00 input[0] 3.18e-04 6.27e04 output encoder.block.2.layer.1.dropout Dropout 3.18e-04 6.27e04 input[0] 0.00e00 inf output报告解读要点如下第一行给出问题出现的批次号Detected inf/nan during batch_number0表示问题发生在第一个批次batch 从 0 开始计数表格结构abs min/abs max是张量所有元素绝对值的极小值与极大值科学计数法metadata列标识张量角色——weight为模块参数input[i]为第 i 个输入output[i]为第 i 个输出无编号的output表示唯一输出模块定位例如encoder.block.2.layer.1.layer_norm T5LayerNorm表示编码器第二个块中第一层的层归一化其forward调用对应类为T5LayerNorm数值趋势观察最后几帧T5DenseGatedGeluDense的输出激活绝对最大值已达约 6.27e04而 fp16 中溢出inf前的最大可表示数字约为 64e36.4e04。fp16 下激活值应远小于 1e4因为矩阵乘法中1e4 * 1e4 1e8会直接触发数值溢出条件。最后一帧Dropout将部分元素置零后重新归一化权重把绝对最大值推过 64K最终产生inf——这说明需要往前看溢出前几帧而不是只看最后一帧。结合模型源码定位根因报告中的encoder.block.2.layer.1.DenseReluDense.dropout等路径可以直接与模型实现代码对应。文档示例给出的是 T5 前馈模块的经典实现在 src/transformers/models/t5/modeling_t5.py 中文档示例的类名T5DenseGatedGeluDense在当前代码库中对应为T5DenseGatedActDense见 modeling_t5.py 中T5LayerFF对is_gated_act的分支选择class T5DenseGatedGeluDense(nn.Module): def __init__(self, config): super().__init__() self.wi_0 nn.Linear(config.d_model, config.d_ff, biasFalse) self.wi_1 nn.Linear(config.d_model, config.d_ff, biasFalse) self.wo nn.Linear(config.d_ff, config.d_model, biasFalse) 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对照这份代码报告中的调用序列一目了然wi_0、wi_1两次线性投影 →T5DenseGatedGeluDense前向门控 GELU 乘积→dropout。问题出在T5DenseGatedGeluDense.forward产生约 62.7K 的激活值后随后的Dropout把最大值推过 fp16 上限。修复方案局部切换 fp32定位到溢出发生的模块后常见的修复是在数值开始变大的前几帧切换到 fp32 计算避免乘法/加法过程中溢出。一种做法是把原来的forward逻辑抽到辅助方法_forward中然后在forward里用torch.amp.autocast(..., enabledFalse)包裹临时关闭自动混合精度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): device_type hidden_states.device.type if torch.is_autocast_enabled(device_type): with torch.amp.autocast(device_type, enabledFalse): return self._forward(hidden_states) else: return self._forward(hidden_states)torch.is_autocast_enabled(device_type)用于判断当前设备类型cuda/cpu是否开启了 autocast只有开启时才显式关闭。当然修复方案不止这一种例如可以临时关闭 AMP 混合精度训练整体排查也可以调整缩放策略或改用 bf16。检测 forward 内部的中间值自动检测器只报告完整帧模块级的输入与输出。如果某个forward内部包含多个计算步骤想知道具体是哪一步产生异常可以使用detect_overflow辅助函数在任意位置手动插入检测点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)这里添加了两个检测点分别检查 layer_norm 之后与 DenseReluDense 之后的forwarded_states是否出现inf/nan。detect_overflowdebug_utils.py会打印形如xxx has nans/xxx has infs的信息并返回布尔值源码中还内置了按阈值统计大元素数量如绝对值超过 100/1000/10000 的元素个数的辅助调试代码可按需开启。调整保存的帧数自定义实例化调试器时可以通过max_frames_to_save调整溢出时输出的帧数默认值为 21from transformers.debug_utils import DebugUnderflowOverflow debug_overflow DebugUnderflowOverflow(model, max_frames_to_save100)更大的帧数可以提供更长的数值演变历史便于观察数值从哪个位置开始失控。特定批次绝对最小/最大值追踪同一个调试类还提供第二种工作模式关闭下溢/溢出检测仅按批次追踪每个forward调用的绝对最小/最大值。这在知道程序在某个批次之后开始异常时尤其有用可以直接把追踪聚焦到目标区域对比数值从何处开始发散。指定要追踪的批次例如只追踪批次 1 和 3 的完整前向过程批次从 0 开始计数debug_overflow DebugUnderflowOverflow(model, trace_batch_nums[1, 3])此时批次 1 和 3 的每个forward帧都会以与检测模式相同的格式输出。样本输出如下中间部分省略*** Starting batch number1 *** abs min abs max metadata shared Embedding 1.01e-06 7.92e02 weight 0.00e00 2.47e04 input[0] 5.36e-05 7.92e02 output [...] decoder.dropout Dropout 1.60e-07 2.27e01 input[0] 0.00e00 2.52e01 output decoder T5Stack not a tensor output lm_head Linear 1.01e-06 7.92e02 weight 0.00e00 1.11e00 input[0] 6.06e-02 8.39e01 output T5ForConditionalGeneration not a tensor output *** Starting batch number3 *** abs min abs max metadata shared Embedding 1.01e-06 7.92e02 weight 0.00e00 2.78e04 input[0] 5.36e-05 7.92e02 output [...]输出中*** Starting batch numberN ***标记批次开始not a tensor output表示该模块返回了非张量输出如包含张量的复杂结构DebugUnderflowOverflow会如实标注而非报错。由于会为模型的每一次forward调用 dump 一帧追踪模式会产生大量输出——这既可能是一种负担也可能比通用调试器更直观例如当问题从批次 150 开始出现时只 dump 批次 149 与 150 的追踪对比两组数据从何处开始不同即可快速锁定。从源码看追踪模式由forward_hook中的trace_mode self.batch_number in self.trace_batch_nums判定处于追踪批次时先清空帧缓冲reset_saved_frames随后逐帧trace_frames()实时打印debug_utils.py而检测逻辑在detected_overflow and not trace_mode时才会触发debug_utils.py两种模式互斥。在指定批次后停止训练还可以通过abort_after_batch_num指定停止训练的批次号debug_overflow DebugUnderflowOverflow(model, trace_batch_nums[1, 3], abort_after_batch_num3)该参数在追踪模式下最常用但任意模式都可以使用。源码中对应的中止逻辑位于 debug_utils.py当self.batch_number self.abort_after_batch_num时抛出ValueError并附上提示信息避免调试脚本无限运行。性能注意事项DebugUnderflowOverflow会在每次forward时对模型的所有权重逐一计算绝对最小/最大值见源码 docstring 的Performance一节这会显著拖慢训练速度。因此务必在调试需求满足后立即移除该模块。官方文档还建议如果要在耗时数小时的长训练中使用检测模式先在追踪模式下对少量批次试运行以确认调试器配置正确例如 debug_utils.py 中的相关提示。DeepSpeed 场景的补充排错路径如果训练启用了 DeepSpeedTrainingArguments.deepspeed参数见 training_args.py遇到报错时应先判断是否为 DeepSpeed 所致去掉 DeepSpeed 重跑一遍若错误依旧说明问题与 DeepSpeed 集成无关。下面列举几个常见问题详见 docs/source/en/debugging.md。启动时进程被杀如果 DeepSpeed 进程在启动阶段没有 traceback 就被杀掉通常是程序申请的内存超过了可用/允许的 CPU 内存上限被操作系统内核直接终止。此时应检查配置文件中是否配置了offload_optimizer、offload_param或两者将参数/优化器状态卸载到 CPU如果环境具备 NVMe 且使用 ZeRO-3可以改为卸载到 NVMe并先估算模型的内存需求。NaN loss 与 fp16 溢出NaN loss常见于模型以 bf16 预训练、却以 fp16 使用的情况TPU 训练的模型尤为常见。此时应改用 fp32或在硬件支持时改用 bf16TPU、Ampere 及更新的 GPU。fp16 本身也容易引发溢出例如下面这种配置{ fp16: { enabled: auto, loss_scale: 0, loss_scale_window: 1000, initial_scale_power: 16, hysteresis: 2, min_loss_scale: 1 } }日志中反复出现的[deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: ..., reducing to ...表示 DeepSpeed 的 loss scaler 找不到能克服损失溢出的缩放系数。文档建议尝试更大的initial_scale_power值32通常有效。这本质上与本文第二部分讨论的溢出问题是同一类数值问题只是发生在 DeepSpeed 的 AMP 缩放层。调试流程速查综合全文遇到分布式训练异常时可按如下顺序排查通信问题运行 scripts/distributed/torch-distributed-gpu-test.py 确认多卡/多机 NCCL 通信与显存分配正常失败时加NCCL_DEBUGINFO获取底层日志数值问题给训练命令追加--debug underflow_overflowTrainer或手动实例化DebugUnderflowOverflow(model)自定义循环等待自动报告定位第一个异常批次与模块定向追踪已知异常批次号时用trace_batch_nums[a, b]对比相邻批次的绝对值演变用abort_after_batch_num提前终止修复与验证在溢出模块处局部切换 fp32或用detect_overflow细化检测点修复后务必移除调试器它会显著拖慢训练DeepSpeed 场景先去掉 DeepSpeed 复现判断责任方再针对启动被杀、NaN loss、fp16 溢出分别按上文方案处理。这套从通信到数值再到定向追踪的调试方法论配合 src/transformers/debug_utils.py 的自动检测能力能够把大海捞针式的排错转化为分钟级定位是日常训练与模型微调中的实用工具。【免费下载链接】transformers Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。