快速结论:当调用 accelerate launch --cpu(走 simple_launcher)启动的子脚本非零退出时,父进程抛出的 subprocess.CalledProcessError 只带返回码和命令,e.stderr 与 e.output 均为 None,真正的 ValueError: the real cause 无法被程序化捕获。优先排查是否在用无 torchrun 的简单启动路径,以及是否已使用包含修复的 accelerate 版本。
适用环境:accelerate 1.12.0(Issue 与 main 分支 b795b4838 行为一致);触发路径为 --cpu + --num_processes 1 的 simple_launcher。Issue 未提供操作系统、Python、CUDA、显卡等具体版本信息。
最快修复方案:暂无确认的一步修复方案(Issue 报告时修复尚未发布)。Issue 中已有 PR #4292 尝试捕获 stdout/stderr 并传入 CalledProcessError,可优先尝试升级到包含该 PR 的 accelerate 版本;否则需要自行在调用侧读取子进程输出。
注意事项:若自行按“选项 1”接管 stderr 管道,必须使用 communicate() 而非 wait() 之后再 read():Issue 评论确认后者会在子进程刷屏 stdout 后死锁,communicate() 在同一场景下 41ms 正常结束并带回完整 traceback。这与多进程路径 multi_gpu_launcher(由 torch.distributed.run 抛出携带根因的 ChildFailedError)的行为差异,是本次问题的核心。
问题场景
用户通过 accelerate launch 使用 simple_launcher 启动自己的训练脚本(复现命令为 --cpu --num_processes 1),并希望在外层代码里 except subprocess.CalledProcessError 后根据异常内容做判断,例如 TRL CLI 测试中按异常类型和消息匹配并重试已知的瞬时基础设施故障。当子脚本因 GPU 显存压力等原因崩溃时,父进程拿到的只有命令和退出码,无法区分临时故障和真实回归。
报错原文
Traceback (most recent call last):
File "boom.py", line 1, in <module>
raise ValueError("the real cause")
ValueError: the real cause
parent sees : CalledProcessError: Command '['.../bin/python', 'boom.py']' returned non-zero exit status 1.
e.stderr : None
e.output : None
原因分析
simple_launcher 用 subprocess.Popen 拉起子进程,子进程 stderr 被继承而不是读取,父进程在非零退出时只用返回码和命令构造 CalledProcessError,既没有传 output 也没有传 stderr,因此调用方只能知道“有东西非零退出”。与之相对,multi_gpu_launcher 委托给 torch.distributed.run,torch elastic 通过 error file 机制把子进程根异常传回父进程,抛出带有 “Root Cause” 段的 ChildFailedError。Issue 认为 simple_launcher 扮演的是同一个 “nanny process” 角色,理应给出同样的保证。子进程 traceback 确实会打到终端,但位置在失败报告上方很多行、且与其他 worker 输出交错,对程序化调用者没有帮助。
环境排查
- 确认 accelerate 版本:Issue 在 1.12.0 和 main(b795b4838eb33bde60eb398649c4ab006874e3e9)上均可复现。
- 确认是否走
simple_launcher:使用--cpu(无torchrun多进程分发)时命中该路径。 - 确认是否命中 PR #4292 的修复:检查所用版本是否已包含“捕获 stdout/stderr 并传入
CalledProcessError”的改动。 - Issue 未提供 Python、CUDA、PyTorch、显卡型号等信息,无法据此进一步缩小范围。
解决步骤
- 先用 Issue 提供的最小脚本确认现象:准备一个只含
raise ValueError("the real cause")的boom.py,用launch_command_parser().parse_args(["--cpu", "--num_processes", "1", sys.argv[1]])并捕获subprocess.CalledProcessError,打印e.stderr与e.output;若两者均为None,即为此问题。 - 优先尝试升级到包含 PR #4292 的 accelerate 版本,让
simple_launcher把子进程输出写入CalledProcessError;升级后重复上一步验证。 - 若暂时无法升级,需要在调用侧自行取得子进程输出:可参考 Issue 提出的“选项 1”思路(将子进程 stderr 经管道转发并保留尾部内容)。实现时必须用
communicate()读取,不要用wait()然后再read()。 - 若需要与
multi_gpu_launcher完全一致的语义(携带 “Root Cause” 的ChildFailedError),则需要“选项 2”:把启动脚本包进 torch elastic 的 error-file 机制,这属于更大的改动,Issue 中列为工作量更高的方案。
验证方法
重新运行上述最小复现:修复生效时,捕获到的 CalledProcessError 的 e.stderr 应包含子进程的完整 traceback 与 ValueError: the real cause,而不是 None;同时父进程终端仍应看到实时输出。Issue 评论提到,用 communicate() 的实现可在 41ms 内结束并带回完整 traceback,可作为参考基准。
参考来源
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


