PyCharm 调试 PyTorch 分布式程序:从 torchrun 启动改为直接调试 main.py
一、问题背景
在使用 PyCharm 调试 PyTorch 分布式训练程序时,常见的启动方式是:
bash
python -m torch.distributed.run --nproc_per_node=1 main.py
或者:
bash
torchrun --nproc_per_node=1 main.py
这种方式在命令行中通常可以正常运行,但在 PyCharm 的 Debug 模式下,经常会出现以下问题:
text
Connected to pydev debugger
程序虽然进入了训练过程,但断点无法命中,表现为"明明点击了 Debug,程序却一直往下跑"。
例如日志中已经可以看到:
text
world_size: 1
local_rank: 0
Epoch[1] Iteration[50/1156]
说明程序已经正常进入训练阶段,但 PyCharm 的断点没有生效。
二、原因分析
造成该问题的核心原因是:
PyCharm 调试器连接到的是 torch.distributed.run 或 torchrun 启动器进程,而真正执行训练代码的 main.py 可能是由启动器进一步拉起的子进程。
也就是说,当使用下面这种方式启动时:
bash
python -m torch.distributed.run --nproc_per_node=1 main.py
PyCharm 首先调试的是:
python
torch.distributed.run
然后该模块再启动真正的训练脚本:
python
main.py
因此,PyCharm 不一定能够稳定接管真正执行训练代码的进程,导致断点无法命中。
另外,训练代码中的 DataLoader 如果设置了多个工作进程,例如:
python
num_workers = 8
也会进一步增加调试复杂度。因为 DataLoader 会额外启动多个子进程,导致 PyCharm 更难准确进入断点。
三、推荐解决方案:调试时不要使用 torchrun,直接运行 main.py
如果当前只是为了 Debug,而不是必须验证多卡通信,那么最稳妥的方式是:
直接让 PyCharm 运行 main.py,并手动补充分布式所需的环境变量。
也就是说,调试阶段不要再使用:
bash
python -m torch.distributed.run ...
也不要使用:
bash
torchrun ...
而是让 PyCharm 直接执行:
bash
python main.py
这样 PyCharm 调试器会直接接管 main.py,断点最容易命中。
四、PyCharm 推荐配置
1. Run 类型
在 PyCharm 的 Run/Debug Configuration 中选择:
text
Script path
不要选择:
text
Module name
因为此时我们不再通过:
bash
python -m torch.distributed.run
启动,而是直接运行训练入口脚本。
2. Script path
填写远程服务器上的 main.py 路径,例如:
bash
/home/XX/XX/main.py
不要填写:
bash
torch.distributed.run
也不要填写:
bash
/root/miniconda3/envs/xxx/bin/torchrun
调试阶段的核心目标是让 PyCharm 直接进入 main.py。
3. Python Interpreter
解释器选择项目所使用的 Python 环境,例如:
bash
/root/miniconda3/envs/xxx/bin/python
注意,解释器必须是 Python,而不是 torchrun。
错误写法:
bash
/root/miniconda3/envs/xxx/bin/torchrun
正确写法:
bash
/root/miniconda3/envs/rt/bin/python
4. Working directory
工作目录填写项目根目录,例如:
bash
/home/xx/xx/xx/project
该目录应当与 main.py、配置文件、数据路径所在项目保持一致。
尤其要注意 Linux 区分大小写,例如下面两个目录可能是不同目录:如果路径不一致,可能导致代码运行的是服务器上的另一份文件,从而出现断点无法命中的问题。
5. Parameters
如果 main.py 不需要额外参数,可以先留空。
如果原本运行命令中有参数,例如:
bash
main.py --config configs/xxx.yaml --output_dir output/debug
则在 Parameters 中填写:
bash
--config configs/xxx.yaml --output_dir output/debug
注意,此时不要再写:
bash
--nproc_per_node=1 main.py
因为 main.py 已经在 Script path 中指定了。
6. Environment variables
由于直接运行 main.py 不再经过 torchrun,所以需要手动补齐分布式环境变量。
单卡 Debug 推荐设置:
bash
CUDA_VISIBLE_DEVICES=2;WORLD_SIZE=1;RANK=0;LOCAL_RANK=0;MASTER_ADDR=127.0.0.1;MASTER_PORT=29501;NCCL_DEBUG=INFO;NCCL_IB_DISABLE=1;PYTHONUNBUFFERED=1
其中:
text
CUDA_VISIBLE_DEVICES=2
表示只使用第 2 张 GPU。
text
WORLD_SIZE=1
表示当前只有 1 个训练进程。
text
RANK=0
表示当前进程是第 0 个进程。
text
LOCAL_RANK=0
表示当前进程在本机使用的 GPU 编号为 0。需要注意,这里的 LOCAL_RANK=0 对应的是 CUDA_VISIBLE_DEVICES 可见范围内的第 0 张卡。若设置 CUDA_VISIBLE_DEVICES=2,那么程序中的 cuda:0 实际对应物理 GPU 2。
text
MASTER_ADDR=127.0.0.1
MASTER_PORT=29501
用于初始化分布式进程组。
text
NCCL_IB_DISABLE=1
用于关闭 InfiniBand,单机服务器或容器环境中通常更稳。
text
PYTHONUNBUFFERED=1
用于让日志实时输出,便于调试观察。
五、调试阶段建议关闭 DataLoader 多进程
如果配置文件中有:
python
num_workers = 8
建议调试时临时改为:
python
num_workers = 0
可以在配置文件中修改:
yaml
num_workers: 0
也可以在 main.py 读取配置后临时加入:
python
config["data"]["num_workers"] = 0
这样做的好处是:
- 避免 DataLoader 启动额外子进程;
- 断点更容易命中;
- 数据读取报错更容易定位;
- PyCharm 调试器不会被多个 worker 进程干扰。
调试完成后,再恢复为:
python
num_workers = 8
六、建议在 main.py 中加入强制断点测试
为了确认 PyCharm 是否真正进入了 main.py,可以在 main.py 开头临时加入:
python
print("===== enter main.py =====")
breakpoint()
如果程序能够在这里停住,说明 PyCharm 已经成功接管 main.py。
如果 breakpoint() 能停,但 PyCharm 红点断点不能停,则一般说明:
text
本地代码和远程执行代码不是同一份
此时需要检查:
text
1. PyCharm 的 Deployment 路径映射
2. Working directory 是否正确
3. Script path 是否指向正确的 main.py
4. 本地代码是否已经同步到远程服务器
5. 远程项目目录名称是否存在大小写或字母混淆
七、如果仍然使用 torch.distributed.run,需要额外设置
如果后续确实需要用下面方式调试:
bash
python -m torch.distributed.run --nproc_per_node=2 main.py
那么 PyCharm 中需要开启:
text
Settings -> Build, Execution, Deployment -> Python Debugger
勾选:
text
Attach to subprocess automatically while debugging
中文界面一般是:
text
调试时自动附加到子进程
但即使开启该选项,torch.distributed.run 加 DataLoader 多进程仍然不如直接运行 main.py 稳定。因此,建议先用单进程方式调通核心代码,再恢复多卡运行。
八、推荐的调试流程
第一步:单卡直接调试 main.py
PyCharm 配置为:
text
Script path:
/home/xxx/xxx/main.py
Working directory:
/home/xxx/xxx
Environment variables:
CUDA_VISIBLE_DEVICES=2;WORLD_SIZE=1;RANK=0;LOCAL_RANK=0;MASTER_ADDR=127.0.0.1;MASTER_PORT=29501;NCCL_DEBUG=INFO;NCCL_IB_DISABLE=1;PYTHONUNBUFFERED=1
同时将:
python
num_workers = 0
在 main.py 开头加入:
python
print("===== enter main.py =====")
breakpoint()
确认断点可以正常停住。
第二步:调试训练逻辑
在以下位置设置断点:
python
run(config)
或者训练循环中:
python
for i, batch in enumerate(train_loader):
也可以在 batch 解包前设置断点,用于查看 DataLoader 返回的数据结构。
例如:
python
for i, batch in enumerate(train_loader):
print("batch type:", type(batch))
print("batch len:", len(batch))
break
第三步:恢复 torchrun 或 torch.distributed.run 多卡运行
当单卡 Debug 没有问题后,再恢复分布式启动方式,例如:
bash
python -m torch.distributed.run --standalone --nnodes=1 --nproc_per_node=2 main.py
或:
bash
torchrun --standalone --nnodes=1 --nproc_per_node=2 main.py
对应环境变量可以设置为:
bash
CUDA_VISIBLE_DEVICES=2,3;NCCL_DEBUG=INFO;NCCL_IB_DISABLE=1
如果只想继续单卡运行:
bash
CUDA_VISIBLE_DEVICES=2
九、常见问题总结
1. 为什么 Debug 模式下程序一直跑?
因为 Debug 模式并不代表程序会自动暂停,只有遇到有效断点才会停。如果使用 torch.distributed.run,PyCharm 可能连接的是启动器进程,而不是实际执行 main.py 的训练进程。
2. 为什么程序能训练,但断点不生效?
常见原因包括:
text
1. PyCharm 没有接管 main.py 子进程;
2. DataLoader 的 num_workers 大于 0;
3. 本地代码和远程代码不同步;
4. Script path 或 Working directory 指向了错误目录;
5. 断点所在代码没有被执行到;
6. 使用了 torchrun,导致真实训练逻辑在子进程中运行。
3. 为什么调试时建议不用 torchrun?
因为单卡 Debug 的目标是排查代码逻辑,不是测试多卡通信。直接运行 main.py 可以让 PyCharm 直接接管训练脚本,断点更稳定。
4. 直接运行 main.py 会不会缺少 WORLD_SIZE?
会,所以需要手动设置:
bash
WORLD_SIZE=1;RANK=0;LOCAL_RANK=0
这样代码中读取分布式环境变量时就不会报错。
5. 为什么 CUDA_VISIBLE_DEVICES=2 时,日志里仍然是 cuda:0?
因为 CUDA_VISIBLE_DEVICES=2 会让程序只看到一张卡,这张可见卡在程序内部编号为 cuda:0,但它实际对应的是物理 GPU 2。
十、最终结论
在 PyCharm 中调试 PyTorch 分布式程序时,推荐遵循以下原则:
text
训练时可以使用 torchrun;
调试时优先直接运行 main.py。
最稳的 Debug 配置是:
text
Script path:
/home/zmt/ai/qg/BiI-RRA/main.py
Interpreter:
/root/miniconda3/envs/rt/bin/python
Working directory:
/home/zmt/ai/qg/BiI-RRA
Environment variables:
CUDA_VISIBLE_DEVICES=2;WORLD_SIZE=1;RANK=0;LOCAL_RANK=0;MASTER_ADDR=127.0.0.1;MASTER_PORT=29501;NCCL_DEBUG=INFO;NCCL_IB_DISABLE=1;PYTHONUNBUFFERED=1
同时,将 DataLoader 的:
python
num_workers = 8
临时改为:
python
num_workers = 0
这样可以最大程度保证 PyCharm 断点能够正常命中,便于定位代码问题。等代码逻辑调试完成后,再恢复 torchrun 或 torch.distributed.run 进行多卡训练。