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



所有评论(0)