LLaMA Factory 多机容器训练模型的爬坑实录

244 阅读26分钟

背景

我想使用 LLamaFactory 对 meta-llama/Meta-Llama-3-8B-Instruct 模型进行多机训练,实现全量微调

主要目的是为了能够熟练搭建多机场景,为后面测试混搭场景(比如在 A100 + H800 等多种类型的机器上进行多机训练)、长时间训练(比如在混搭场景下,能够训练满 72 小时不崩)提供基础


准备工作

硬件设备:2 台 H800 GPU 服务器(每台都是 8 卡)

硬件环境:driver 580.95.05 + nvcc 12.9

容器环境:ubuntu 22.04 + cuda 12.8 + gcc 13.3.0 + python 3.12.3 + pytorch 2.5.1

LLaMA Factory 版本:0.9.4.dev0


单机训练

先在两台机器上分别跑一次单机训练,简单验证一下环境有没有问题,能不能正常训练,这个还是蛮顺的,基本一次通过

启动容器

我在两台机器上分别启动了容器,通过下面的命令将 GPU 挂载到容器中,命令如下:

sudo docker run --network=host --device=/dev/infiniband --ulimit memlock=-1 \  
--ulimit stack=67108864 --gpus=all --name=jojo_llamafactory --init -it \  
--shm-size=4g 镜像名称 bash  

注意:网络使用 host 模式,后面配置免密登陆也会使用这个模式去进行配置,而且也方便进行多级训练,后面会说到

下载模型

使用的是 meta-llama/Meta-Llama-3-8B-Instruct 模型,直接从 modelscope 上下载

  1. 先安装 modelscope 第三方库,执行 pip install modelscope 命令
  2. 直接下载模型到对应目录下,可以 cd 到想要下载的模型目录下,执行这个命令
# 将 Llama3 8B Instruct 模型下载到当前路径下的 Meta-Llama-3-8B-Instruct 目录里
modelscope download --model LLM-Research/Meta-Llama-3-8B-Instruct --local_dir ./Meta-Llama-3-8B-Instruct
  1. 安装 LLaMA Factory,进行微调
git clone https://github.com/hiyouga/LLaMA-Factory.git  
cd LLaMA-Factory  
# 后面多机训练,会用到 deepspeed,所以配置环境时一块儿都配了
pip install -e ".[torch,metrics,deepspeed]" --no-build-isolation   
执行 llamafactory-cli version 命令,验证是否安装成功  
  1. 修改 yaml 文件,LLaMA Factory 里提供的 yaml 模版中,模型路径使用的是 HuggingFace 上的模型路径,这里修改为本地路径,否则后续在训练时,会自动从 HuggingFace 上拉取模型,如果你没有配置科学上网,直接就运行不起来了
vim examples/train_lora/llama3_lora_sft.yaml  
# 替换为本地模型的绝对路径
将 model_name_or_path 的字段值,修改为 /workspace/jojo/LLM-Research/Meta-Llama-3-8B-Instruct  

启动训练

直接执行 llamafactory-cli train examples/train_lora/llama3_lora_sft.yaml 启动训练就可以了

如果想要使用不同的卡去跑,执行训练命令时,在前面添加 CUDA_VISIBLE_DEVICES 环境变量,使用这种方式去指定进行单机训练时使用的 GPU 卡


多机训练

好的,开始爬坑,搭建多机训练环境,先记录一下我的探索过程,因为没有之前没有实操过,所以对这个一直都是一知半解的状态,所以边解决问题,边深刻理解,最后终于顿悟,如果想直接看如何搭建多机训练环境,直接看最后的总结就好了

第一次尝试(物理机之间配置完免密登陆后,直接运行,失败了)

网上搜了一下,说是训练前,需要对两台机器进行 ssh 免密登陆配置,具体如何配置,可以查看我这个笔记: 配置 SSH 免密登录实现在物理机之间,以及容器之间,如何配置 SSH 免密登录,为之后,比如在进行多机训练时,搭建基础 - 掘金

因为之前没有搞过多机训练,以为和单机差不多,再加上单机训练的时候,没遇到什么问题,以为多机训练也是绵绵的,所以配置完免密登陆后,进入容器,直接按照 LLaMA Factory 文档中给出的命令直接执行

在 A 机器上的容器 container_a 中执行命令(A 机器的 IP 为 192.168.0.1,将其作为主节点,多机训练时,指定的端口为 29500):

FORCE_TORCHRUN=1 NNODES=2 NODE_RANK=0 MASTER_ADDR=192.168.0.1 MASTER_PORT=29500 llamafactory-cli train examples/train_full/llama3_full_sft.yaml  

在 B 机器上的容器 container_b 中执行命令(B 机器的 IP 为 192.168.0.2,将其作为从节点):

FORCE_TORCHRUN=1 NNODES=2 NODE_RANK=1 MASTER_ADDR=192.168.0.1 MASTER_PORT=29500 llamafactory-cli train examples/train_full/llama3_full_sft.yaml  

注意:我这里使用的是全量微调,和单机使用的 yaml 不一样,所以这个 yaml 文件里的模型路径,也要和上面一样改为本地路径

结果执行过程中,报错了:

[rank8]: torch.distributed.DistBackendError: NCCL error in: ../torch/csrc/distributed/c10d/ProcessGroupNCCL.cpp:2777, remote process exited or there was a network error, NCCL version 2.21.5  
[rank8]: ncclRemoteError: A call failed possibly due to a network error or a remote process exiting prematurely.  
[rank8]: Last error:  
[rank8]: Attempt to use communicator before the previous operation returned ncclSuccess  

第二次尝试(尝试在大佬肩膀上狂奔,失败了)

第一次尝试失败后,感觉没这么简单,所以就在知乎上搜到一个多机训练的帖子,按照那里的去进行尝试了下,结果还是失败,我参考的是这个帖子 zhuanlan.zhihu.com/p/201496445…

但是按照他说的操作执行完后,直接运行第一步中的多机训练命令,结果失败了,显示这个错误,和第一次差不多,感觉像是环境没搭建好,导致两台机器不能进行 NCCL 通信:

[rank8]: torch.distributed.DistBackendError: NCCL error in: ../torch/csrc/distributed/c10d/ProcessGroupNCCL.cpp:2777, remote process exited or there was a network error, NCCL version 2.21.5  
[rank8]: ncclRemoteError: A call failed possibly due to a network error or a remote process exiting prematurely.  
[rank8]: Last error:  
[rank8]: Attempt to use communicator before the previous operation returned ncclSuccess  

网上搜了一圈,说是启动容器的时候,要使用 host 网络,不要使用帖子中说的 overlay 网络,因为使用 overlay 网络时很可能会干扰 NCCL 进行通信,优先使用 host网络

然后我把容器 rm 掉,使用 host 网络重新启动了一下,然后再按照帖子中的步骤去搭建环境,但是操作完后,发现容器间 ssh 免密登陆的配置失败了,我不死心,直接运行,不出意外,报同样的错误

第三次尝试(使用 RDMA 的方式,成功了!?)

第二次尝试失败后,我觉得必须得配置好免密登陆才行,具体的配置方法,可以查看我这个笔记:配置 SSH 免密登录实现在物理机之间,以及容器之间,如何配置 SSH 免密登录,为之后,比如在进行多机训练时,搭建基础 - 掘金

但是我当时只配置好了物理机之前的免密,容器间的 ssh 免密登录依旧没配置好(那会儿还没探索出如何配置容器间的 ssh 免密登录),搜了很多方法,就是没配置成功,但是柳暗花明,我搜到了其他的多机训练方法,就是走 RDMA 的方式去进行多机训练,在执行多机训练的命令前,需要配置这几个环境变量:

# 在 container_a 和 container_b 容器中分别执行了以下命令  
  
# 这个用于指定通信网卡,我使用的是 bond0,具体可以执行 ifconfig 命令查看  
export NCCL_SOCKET_IFNAME=bond0  
# 可选项,这个配不配置都行,主要是用于打印 NCCL 日志的,我这里设置为 INFO  
export NCCL_DEBUG=INFO  
# 这个表示启用 RDMA  
export NCCL_IB_DISABLE=0  
# 替换为实际的 RDMA 网卡 HCA ID,可使用 ibv_devices 命令去查看  
export NCCL_IB_HCA=mlx5_0,mlx5_1  

等配置好后,再执行第一步的多机训练命令,然后就能成功执行了

注意,这里有一个误区,SSH 免密登陆和走 RDMA 方式去进行多机训练,是 independent,后面会进行总结,若想了解,可以直接看附录部分

第四次尝试(好像还不成功?)

虽然第三次使用 RDMA 的方式成功了,但是我依旧想使用配置 SSH 免密登陆的方式,去进行多机训练(在这之前,我进入了一个误区,我以为手动启动训练命令的方式,总共有两种,一种是走 RDMA,一种是走 SSH 免密)。

首先解决的就是容器间如何配置免密登陆,因为不甘心之前几次尝试了很多方法,都没有配置成功,这次一定要配置好,通过不断的搜索,不断的尝试,总算找到了对应的方法,都在这个笔记里面了: 配置 SSH 免密登录实现在物理机之间,以及容器之间,如何配置 SSH 免密登录,为之后,比如在进行多机训练时,搭建基础 - 掘金

这次总算配置好了,直接开始执行第一步的命令,好的,失败了,真的是服了,还是报这个错误:

[rank0]:[E1030 07:53:06.757012684 ProcessGroupNCCL.cpp:542] [Rank 0] Collective WorkNCCL(SeqNum=3, OpType=BROADCAST, NumelIn=525336576, NumelOut=525336576, Timeout(ms)=180000000000) raised the following async exception: NCCL error: remote process exited or there was a network error, NCCL version 2.21.5  
ncclRemoteError: A call failed possibly due to a network error or a remote process exiting prematurely.  
Last error:  
NET/IB: Got completion from peer 33.254.160.139<53475> with status=5 opcode=32577 len=0 vendor err 249 (Send) localGid ::ffff:33.255.199.76 remoteGids::ffff:33.254.160.139 hca mlx5_bond_1  
Exception raised from checkForNCCLErrorsInternal at ../torch/csrc/distributed/c10d/ProcessGroupNCCL.cpp:2027 (most recent call first):  
frame #0: c10::Error::Error(c10::SourceLocation, std::string) + 0x96 (0x7f44cbb6c446 in /usr/local/lib/python3.12/dist-packages/torch/lib/libc10.so)  
frame #1: c10d::ProcessGroupNCCL::checkForNCCLErrorsInternal(std::shared_ptr<c10d::NCCLComm>&) + 0x220 (0x7f44815cbf80 in /usr/local/lib/python3.12/dist-packages/torch/lib/libtorch_cuda.so)  
frame #2: c10d::ProcessGroupNCCL::WorkNCCL::checkAndSetException() + 0x7c (0x7f44815cc1cc in /usr/local/lib/python3.12/dist-packages/torch/lib/libtorch_cuda.so)  
frame #3: c10d::ProcessGroupNCCL::watchdogHandler() + 0x213 (0x7f44815d3b93 in /usr/local/lib/python3.12/dist-packages/torch/lib/libtorch_cuda.so)  
frame #4: c10d::ProcessGroupNCCL::ncclCommWatchdog() + 0x14d (0x7f44815d561d in /usr/local/lib/python3.12/dist-packages/torch/lib/libtorch_cuda.so)  
frame #5: <unknown function> + 0x145c0 (0x7f44cc04c5c0 in /usr/local/lib/python3.12/dist-packages/torch/lib/libtorch.so)  
frame #6: <unknown function> + 0x9caa4 (0x7f44cc89caa4 in /lib/x86_64-linux-gnu/libc.so.6)  
frame #7: __clone + 0x44 (0x7f44cc929a34 in /lib/x86_64-linux-gnu/libc.so.6)  
  
[rank0]:[E1030 07:53:06.758678553 ProcessGroupNCCL.cpp:1785] [PG ID 0 PG GUID 0(default_pg) Rank 0] Exception (either an error or timeout) detected by watchdog at work: 3, last enqueued NCCL work: 13, last completed NCCL work: 2.  

但是静下心来一想,我虽然配置好了免密登陆,但是 SSH 端口使用的 2222,不是默认的 22(因为我使用的是 host 网络启动的容器,22 端口已经被物理机占用了);而且 LLamaFactory 使用的是 deepspeed 的方式去进行多机训练的(examples/train_full/llama3_full_sft.yaml 文件中有指定),是不是 deepspeed 没有配置好。然后我就以这个方向开搜,寻找解决办法

随着疯狂在网上找对应的解决方法,配置 /etc/hosts,配置 ~/.ssh/config,编写 hostfile.txt 文件(搞笑的是我当时编辑好后都不知道怎么用,因为一直使用的是 llamafactory-cli 命令来进行训练的,我都不知道怎么传进去,因为没搜到 yaml 文件中有指定 hostfile 文件路径的字段,以为只需要在配置好后,将其放在当前工作目录中就可以了),编译安装 pdsh,还是报同样的错误,始终运行不起来,折磨的我快崩溃了

直到我在搜索过程中,看到一句话:ssh 免密属于控制层面,用于主节点向从节点发起远程命令,自动启动训练进程;rdma 属于数据平面,仅负责 GPU 之间的高性能数据通信,不参与进程启动逻辑

然后,我好像悟了

第五次尝试(终于成功了)

上一次尝试失败后,我觉得并不是我搭建的环境有问题,而是机器有问题,所以使用 mpirun + nccl tests 的方式去进行检测(本来想自己去检测的,但是按照网上搜的编译安装方法,编译安装完后,执行多机 allreduce 命令一直没运行起来,最后只能拜托运维同学帮忙看了下,后面有时间我会再研究下),发现是这两台机器其中有一台的 RDMA 没有测试通,然后我换了一台机器,直接执行第一次尝试中的多机训练命令,成功了!(这里就有一个疑问,既然有一台 RDMA 没有测试通,为什么之前使用 RDMA 的方式执行起来了?在最后的附录里,我会详细说一下)

为了进一步验证我的猜想,我不使用第一次尝试中,分别在两个容器中执行命令的方式,而是直接在 master 容器中(container_a)使用 deepspeed 命令去进行多机训练,此时之前探索过程中学习到的知识就派上用场了,具体操作步骤为:

  1. 配置 /etc/hosts 文件,在里面添加以下内容
192.168.0.1 node1  
192.168.0.2 node2  
  1. 编写 hostfile.txt 文件,在里面添加以下内容
# 指定每个计算节点可参与训练的GPU数量
node1 slots=8  
node2 slots=8  
  1. 使用 deepspeed 进行多机训练,需要安装 pdsh,使用 apt/yum 的方式安装,可能会报各种依赖问题,甚至 pdsh -V 都执行不起来(亲测是这样子的),所以直接编译安装,具体操作可以看这篇笔记 编译安装 pdsh在使用 deepspeed 进行多机训练的时候,会用到 pdsh。若使用 apt/yum 方式去安装 - 掘金
  2. 最后我们执行 deepspeed 多机训练命令进行训练,只需要在 master 容器中执行以下命令
# 使用的还是 LLaMA Factory 中的提供的工具,这时候我才意识到,没必要非要使用 llamafactory-cli 命令去执行训练命令
# --hostfile hostfile.txt 指定 hostfile.txt 文件  
# --ssh_port 2222 指定 SSH 端口,因为我在搭建容器中的免密登陆时,使用的 SSH 端口是 2222  
deepspeed --hostfile hostfile.txt --ssh_port 2222 src/train.py \  
--stage sft \  
--do_train True \  
--model_name_or_path /workspace/jojo/LLM-Research/Meta-Llama-3-8B-Instruct \  
--preprocessing_num_workers 16 \  
--finetuning_type full \  
--template llama3 \  
--flash_attn auto \  
--dataset_dir data \  
--dataset identity,alpaca_en_demo,alpaca_zh_demo \  
--cutoff_len 2048 \  
--learning_rate 5e-05 \  
--num_train_epochs 3.0 \  
--max_samples 100000 \  
--per_device_train_batch_size 2 \  
--gradient_accumulation_steps 8 \  
--lr_scheduler_type cosine \  
--max_grad_norm 1.0 \  
--logging_steps 5 \  
--save_steps 100 \  
--warmup_steps 0 \  
--packing False \  
--enable_thinking True \  
--report_to none \  
--output_dir saves/Llama-3-8B-Instruct/lora/train_2025-10-30-09-38-43 \  
--bf16 True \  
--plot_loss True \  
--trust_remote_code True \  
--ddp_timeout 180000000 \  
--include_num_input_tokens_seen True \  
--optim adamw_torch \  
--lora_rank 8 \  
--lora_alpha 16 \  
--lora_dropout 0 \  
--lora_target all \  
--deepspeed examples/deepspeed/ds_z3_config.json  
  1. 然后,终于训练成功了 !!!

总结

多机训练的方式,总共有两种。一种是多节点手动启动训练命令的方式;另一种则是仅在主节点执行多机训练的命令

1. 多节点手动启动训练命令

这种方式,本质上是“手动分布式”,而非通过主节点统一拉起。因此不需要配置 SSH 免密,这种方式下,只需要满足以下这几个条件

1.1 显示指定分布式参数

每个节点的启动命令必须明确自身角色、其他节点地址以及通信端口

# 以 PyTorch 为例
'''
关键参数:
    --node_rank 区分节点序号(0 为主节点)
    --master_addr 主节点 ip/hostname
    --master_port 主节点监听端口(需要开发防火墙)
'''

# 在 node1(主节点)执行(假设总进程数 16,node1 运行 rank 0 ~ 7)
python -m torch.distributed.launch --nproc_per_node=8 --nnodes=2 \ 
    --node_rank=0 --master_addr="node1" --master_port=29500 \ 
    src/train.py ...
# 在 node2(从节点)执行(运行 rank 8 ~ 15)
python -m torch.distributed.launch --nproc_per_node=8 --nnodes=2 \ 
    --node_rank=1 --master_addr="node1" --master_port=29500 \ 
    src/train.py ...
    
# 在 LLaMA Factory 中
#    其实本质上是一致的,只不过是通过环境变量的方式去进行传入
'''
NODE_RANK 就对应上面的 --node_rank 参数
MASTER_ADDR 和 MASTER_PORT 分别对应上面的 --master_addr 和 --master_port 参数
'''

# 在 node1(主节点)执行
FORCE_TORCHRUN=1 NNODES=2 NODE_RANK=0 MASTER_ADDR=192.168.0.1 MASTER_PORT=29500 llamafactory-cli train examples/train_full/llama3_full_sft.yaml  
# 在 node2(从节点)执行
FORCE_TORCHRUN=1 NNODES=2 NODE_RANK=1 MASTER_ADDR=192.168.0.1 MASTER_PORT=29500 llamafactory-cli train examples/train_full/llama3_full_sft.yaml  
1.2 节点间网络端口互通

主节点的 master_port(如 29500)需对所有从节点开放,从节点需能访问主节点的该端口(可通过 telnet node1 29500 验证连通性)

1.3 配置与环境完全一致

所有节点的代码、模型路径、数据集路径、依赖库版本必须完全相同,否则会因文件缺失导致进程崩溃

2. 仅在主节点执行多机训练的命令

若使用 DeepSpeed、torch.distributed.launch(带 --hosts 参数)、Slurm 等工具,仅在主节点执行一条命令即可自动拉起所有节点的进程,此时 ssh 免密是必需的(具体的执行命令,可以参考最后一次尝试)

2.1 为何需要 ssh 免密?

这类工具的工作原理是:主节点通过 ssh 远程登录到从节点(DeepSpeed 为啥需要 pdsh,就是因为它要通过 pdsh 来登录从节点),并在从节点上自动启动训练进程

例如上面 deepspeed 命令中,有个 --hostfile 参数来指定 hostfile.txt 文件。

  1. DeepSpeed 会解析 hostfile.txt 中的 node1 和 node2(node1,node2 是从哪里来的,就是从 /etc/hosts 中来的,是不是都顺起来了)
  2. 通过 pdsh 免密登录到 node2,并在 node2 上启动分配的进程(如 rank 8 ~ 15)
  3. 若 ssh 免密未配置,主节点会卡在 “输入 node2 密码” 的步骤,导致从节点进程无法启动,这时就得你一遍又一遍的输入登录密码,听着就累
2.2 典型工具依赖 ssh 免密的场景

DeepSpeed:通过 --hostfile 或 --hosts 参数指定节点时,需 ssh 免密

torch.distributed.launch:使用 --hosts=node1,node2 时,需 ssh 免密

Horovod:通过 horovodrun -np 16 -H node1:8,node2:8 ... 启动时,需 ssh 免密

3. 多机训练两种方式的优缺点

优缺点

优点

缺点

多节点手动启动训练命令无需依赖 ssh 免密,适合临时调试或节点间无法配置 ssh 的场景需手动在每个节点执行命令,容易因参数输入错误(如 node_rank 填写反了)导致启动失败;节点数量多时效率极低
仅在主节点执行多机训练的命令自动化程度高,一条命令即可启动所有节点,适合生产环境依赖 ssh 免密配置,且需确保主节点能通过 ssh 访问所有从节点(可通过 ssh node2)测试免密是否生效


附录

现在知道如何进行单机训练和多机训练了。但是再回想之前多次尝试多机训练的过程,就有几个问题,让我没想通,在这里统一记录下

1. RDMA 是什么?NCCL 是什么?他们的关系是怎样的?在多机训练中,充当的是怎样的角色?

1.1 RDMA

RDMA 也是网卡,它和普通网卡的区别就在于他能直接将内存数据 copy 到网卡中。 传统网络需要经过 “应用内存 ——> 内核缓冲区 ——> 网卡缓冲区” 三次拷贝,RDMA 则是只需要经过 “应用内存 ——> 网卡” 一次拷贝(网卡则是直接把数据写到对方内存,也就是说,使用 RDMA 网卡,数据直接从本机内存传输到目标机器内存里,不需要经过 CPU,因此,在使用 rdma 去传输数据的时候,效率要比普通网卡传输数据时高的多,CPU 的使用率也是相当的低)。内核缓冲区本质是 OS 内核管理的一块内存区域,专门用于临时存放硬件(如网卡、磁盘)与应用程序之间传输的数据。如果走普通网络,若是经过应用层、传输层、网络层等协议栈的时候,要加头,都是在这里面去加的。

但是需要注意的是,在使用 RDMA 网卡传输数据时,CPU 也是会参与的,只是参与的少,并不代表完全不参与。使用 RDMA 只是实现了控制面(CPU)与数据面(硬件)彻底解耦,让 CPU 从“重复的数据搬运”中解放出来,专注于复杂的业务逻辑。在开始传输数据前,CPU 只负责告诉 RDMA 网卡目标地址与权限,其他的就不管了,实际数据搬运完全交给硬件

RDMA 与普通网卡的本质差异,就在于主动内存访问 vs 被动数据包转发。普通网卡的工作模式是被动接收指令,CPU 让它干啥,他就干啥。而 RDMA 则是 CPU 告诉它把数据写入对方服务器的内存地址 0x7f123456 里面去,网卡就会主动通过网络查询对方是否授权访问该地址(类似检查快递收货码);直接从本地内存读取数据,写入对方内存(无需对方 CPU 配合);完成后通过中断通知 CPU(可选,类似异步回调)

所以说,RDMA 就是用来进行数据传输的,和 ssh 免密登录没啥关系

1.2 NCCL

NCCL 可以理解为 GPU 通信的专用编辑器,核心价值在于把分布式训练的通信需求(如 allreduce)翻译成最优的硬件操作序列。 也就是说,他是用来管理 rdma 的,但是它很强大,并不是只用来管理 RDMA。如果在单机内,他还会看有没有 NVLink,要不要走 PCIE(这样 CPU 就参与进来了),还会识别 GPU 互联拓扑(比如在 8 卡服务器内,环形 NVLink vs 全连接 NVLink);如果在多机间,它会探测 RDMA 网络的交换机层级,避免跨层级传输(类似后端服务避免跨地域调用);除此之外,还会根据 RDMA 带宽动态调整分片大小(即数据自适应分片),类似动态负载均衡,小分片适合低延迟场景(比如参数广播),大分片适合高带宽场景(比如梯度同步)

也就是说,NCCL 会根据实际的情况,动态的去识别当前环境,然后去选择最优传输路径,最大化的利用资源,提高通信效率

1.3 多机训练梯度同步流程

  1. PyTorch DDP 调用 loss.backward() 计算梯度 ——> 梯度保存在 GPU 显存
  2. NCCL 收到 Allreduce 请求 ——> 自动选择通信路径(单机内用 NVLink,跨机用 RDMA)
  3. NCCL 调用 RDMA 的 GPUDirect 技术 ——> 无需 GPU 中转,RDMA 网卡直接读写远端 GPU 显存
  4. 传输前:CPU 一次性注册内存、交换密钥(准备阶段);传输中:CPU 不参与,网卡硬件完成梯度同步

2. RDMA 在多机训练的过程中,是必须的吗?如果没有 RDMA,我能不能进行多机训练?此时 NCCL 还会继续工作吗?

RDMA 并非是多机训练的必需品,但它是突破性能瓶颈的关键选择。就像后端系统用 Redis 缓存加速数据库访问,但是要是没有 Redis 系统也能跑,无非就是性能会降低一些。所以通常大厂里的 GPU 机器,都是有这个的。

如果没有 RDMA,那么多机训练的时候,就会变成这样子:

  1. 数据路径:GPU 计算出的梯度需先 copy 到 CPU 内存,再通过内核协议栈处理(TCP 校验和、路由等),最后通过普通网卡发送
  2. CPU 占用:TCP 协议栈处理会占用 30%+ 的 CPU 核心,导致 GPU 算力闲置(就像快递员总占用电梯,其他人只能干等)
  3. 实际效果:2 机训练速度可能仅比单机快 1.5 倍(理论应该快 2 倍),当扩展到 16 机时效率可能跌破 50%

注意:此时 NCCL 还会工作,但会自动降级为 TCP 通信模式,就像导航软件发现高速封路,然后自动切换到国道,能到达终点,但耗时会翻倍

3. 为什么刚开始那两台 GPU 机器,虽然 RDMA 有问题,但是为什么设置了那几个环境变量就能跑起来了

3.1 背景

刚开始我操作的两台 GPU 机器,其中有一台 GPU 机器的 RDMA 有问题,当时不知道,后来运维同学告诉我我才知道,但是当时我配置了这三个环境变量(NCCL_SOCKET_IFNAME=bond0 NCCL_IB_DISABLE=0 NCCL_IB_HCA=mlx5_0,mlx5_1),居然能正常进行多机训练了。我后来回想到这里的时候,有点儿费解,然后就去搜了一下

3.2 可能原因

NCCL 像一个 “自动选路的快递系统”,但它需要你告诉它可用的快递网点(网卡)在哪?允许走高速专线(RDMA)还是只能走普通公路(TCP)?控制中心(管理通信)该连哪个网络接口?

默认情况下,NCCL 会自动探测这些信息,但在复杂环境(如多网卡、bonded 接口、混合 RDMA/以太网)中,它很容易 “猜错”,导致通信失败(我的两台 GPU 机器就是在这个复杂环境里,有一堆的网络接口)。而我设置的这三个环境变量正是帮 NCCL 纠正了 “猜错” 的配置。

3.3 环境变量

NCCL_SOCKET_IFNAME=bond0 用于指定控制通道用那个网络接口。NCCL 通信分两种,一种是数据通道(高带宽梯度传输,走 RDMA/NVLink);另一种则是控制通道(低带宽管理消息,如握手、错误通知,默认用 TCP socket 实现)。如果服务器有多个网络接口(如 eth0、eth1、bond0、bond1),NCCL 默认可能会选一个看似可用但实际不可达的接口(比如 eth0 只连内网管理网,不通其他训练节点,确实默认会走这个)。而 bond0 通常是业务数据网的绑定接口(多网卡聚合,高可用/高带宽),显式指定它作为控制通道的接口,相当于告诉 NCCL,用这个接口和其他节点的控制中心通信,别瞎猜了。就像微服务需要 REGISTRY_HOST=service-registry.bond0 来指定注册中心地址,避免服务连到错误的 registry 导致服务发现失败。

NCCL_IB_DISABLE=0 则是启用 RDMA 通信,别走 TCP 降级。IB 这里是广义的 RDMA 支持(包括 InfiniBand/RoCE)。NCCL 默认逻辑是:

  1. 如果探测到 RDMA 网卡(如 Mellanox mlx5 系列),则优先用 RDMA(性能好)。
  2. 如果探测失败(如驱动没装、权限不足、交换机配置问题),会自动降级到 TCP socket(性能差,但能通)。

但问题在于,探测可能 “假失败”。比如:

  1. RDMA 网卡驱动版本与 NCCL 不兼容(如你用 NCCL 2.21.5,但 Mellanox 驱动太旧)。
  2. 服务器有 RDMA 网卡,但被运维禁用了部分功能(如 RoCEv2 协议未启用)。

此时 NCCL 会误判 RDMA 不可用,强制降级到 TCP。但 TCP 带宽低、延迟高,在多机训练时可能因超时导致远程进程退出(即错误日志输出中的 remote process exited 错误)。而 NCCL_IB_DISABLE=0 显式告诉 NCCL,强制启用 RDMA,别降级到 TCP,我确认 RDMA 硬件可用。就像用 USE_KAFKA=1 强制服务用 Kafka 而非本地文件队列,避免因 Kafka 临时连接波动导致服务降级到低效模式。

NCCL_IB_HCA=mlx5_0,mlx5_1 则是指定了 RDMA 网卡硬件是谁。HCA(Host Channel Adapter)即 RDMA 网卡的硬件标识(如 Mellanox 卡通常命名为 mlx5_0、mlx5_1)。如果服务器有多个 RDMA 网卡(或同时存在 RDMA 和普通网卡,我的 GPU 机器环境是这样的),NCCL 可能会:

  1. 选到一个未配置的 RDMA 网卡(如 mlx5_2 实际未插光纤)。
  2. 试图用不支持 RDMA 的普通网卡(如把 eth0 误认成 RDMA 网卡)。

NCCL_IB_HCA=mlx5_0,mlx5_1 显式指定了只使用这两个 RDMA 网卡,避免 NCCL 尝试无效硬件导致通信超时。就像你用 ELASTICSEARCH_HOSTS=es-node1,es-node2 指定 Elasticsearch 集群节点,避免客户端连接到已下线的节点。

3.4 场景复现,猜测可能原因

我的环境:

  1. 网卡1:eth0(1G 管理网,仅连内网运维平台,不通其他训练节点)。
  2. 网卡2:bond0(10G 业务网,连所有训练节点,用于数据通信)。
  3. RDMA 网卡:mlx5_0/mlx5_1(200G RoCE 网卡,插光纤连训练集群交换机)。

(除了上面这几个,还有好多,不知道为啥一台 GPU 机器要安装这么多网卡,我只单独拎出来这几个)

NCCL 默认探测的错误路径

  1. 控制通道错误:NCCL 自动选了 eth0 作为 TCP 控制接口,但 eth0 不通其他训练节点 → 握手失败,触发 remote process exited。
  2. RDMA 禁用:因 mlx5_0 驱动配置问题(如 RoCEv2 未启用),NCCL 误判 RDMA 不可用 → 降级到 TCP,带宽不足超时。
  3. HCA 选错:NCCL 尝试使用未插光纤的 mlx5_2 → 物理链路不通,通信失败。

我设置完环境变量后,就变成这样:

  1. 控制通道走 bond0(通训练节点)。
  2. 强制启用 RDMA(不降级 TCP)。
  3. 只使用 mlx5_0/mlx5_1(已配置 RoCE 的可用 RDMA 网卡)

4. mpirun + nccl tests 测试验证的是 RDMA 还是 NCCL?

mpirun + nccl tests 测试的是 NCCL 软件层在特定硬件(包括 RDMA)上的通信性能,而非直接测试 RDMA 硬件本身。可以把它理解为端到端的分布式通信全链路测试。RDMA 只是这条链路中可能被调用的底层硬件之一,而 NCCL 是负责协调这条链路的核心软件。

若 mpirun + nccl tests 测试失败:

  1. 单机场景:检测 NVLink/PCIE (与 RDMA 无关)
  2. 多机场景:看 NCCL 日志用了 RDMA 还是 TCP,再去定位硬件
  3. 所有路径都通但是性能极差:可能是软件配置有问题

参考链接

zhuanlan.zhihu.com/p/201496445…

zhuanlan.zhihu.com/p/621004570

zhuanlan.zhihu.com/p/622051995