【Bug已解决】How to specify different number of process per node 解决方案
2026/8/2 7:19:26 网站建设 项目流程

【Bug已解决】How to specify different number of process per node 解决方案

一、现象长什么样

做多机分布式训练时,集群里不同节点的 GPU 数量不一样(比如 node0 有 8 卡、node1 只有 4 卡,或者某节点有空闲卡数不同)。用accelerate launch时,所有人都被要求填一个统一的num_processes/nproc_per_node,于是:

期望:node0 起 8 个进程、node1 起 4 个进程(异构节点) 实际:accelerate 强制所有节点用同一个 nproc_per_node 现象:节点 GPU 数不一致时报错、或空闲卡浪费、或进程数算错

典型报错:

ValueError: num_processes 必须等于 num_machines * nproc_per_node # 但你的节点 GPU 数不均,无法用单一 nproc_per_node 表达

最小判据:

触发:异构节点(每节点 GPU 数不同)做多机训练 现象:accelerate 要求统一 nproc_per_node,无法满足 根因:accelerate launch 的默认模型假设"每节点进程数相同" 影响:异构集群无法用 accelerate launch 一键起

最迷惑的是:单节点 / 同构多节点一切正常,accelerate launch文档里满屏都是nproc_per_node,但一旦节点 GPU 不均就傻眼——因为 accelerate 的"每机同进程数"假设不满足。

二、背景

accelerate launch的进程模型是:

total_processes = num_machines * nproc_per_node

它隐含假设每个节点的进程数相同machine_rank用来区分节点(0, 1, 2...),但每节点起几个进程由全局的nproc_per_node决定,无法按节点分别指定。

而真实集群常有异构:

  • 不同代机器 GPU 数不同(8 卡 A100 机 + 4 卡 T4 机混用);
  • 某些节点被其他任务占了部分卡,可用卡数少于满配;
  • 调试时只想在部分节点用少量卡。

此时"每机同进程数"不成立。标准解法是绕过 accelerate launch 的统一假设,改用torchrun在每个节点独立指定自己的nproc_per_node:torchrun 是 per-node 启动器,每个节点各自跑一条命令,自己决定起几个进程、用什么nnodes/node_rank。accelerate 仍然在训练脚本里负责Accelerator初始化,只是"启动"这步交给 torchrun 分别控制。

根因是"accelerate launch 的统一 nproc_per_node 模型无法表达异构节点"。

三、根因

抽象成代码(示意):

# accelerate launch 的假设(问题所在) def total_processes(cfg): # 假设每节点进程数相同 return cfg.num_machines * cfg.nproc_per_node # 异构时不成立 # 异构集群:node0=8 卡,node1=4 卡 # 无法用单一 nproc_per_node 表达 -> ValueError

根因链条:

  1. accelerate launchnum_machines * nproc_per_node算总进程;
  2. 它要求所有节点nproc_per_node一致;
  3. 异构节点(每节点 GPU 数不同)不满足该假设;
  4. 要么报错、要么强行统一导致空闲卡浪费;
  5. torchrun 是 per-node 启动器,可每节点独立指定nproc_per_node,绕开该限制。

一句话:accelerate launch 的"每机同进程数"模型无法表达异构节点,需用 torchrun 每节点分别指定。

四、最小可运行复现

用纯 Python 模拟"统一 nproc 假设在异构下算错":

# repro_hetero_nodes.py def total_processes_uniform(nodes, nproc_per_node): return nodes * nproc_per_node def main(): # 异构:node0=8 卡,node1=4 卡 gpus_per_node = [8, 4] try: # 强行用统一 nproc_per_node=8 -> node1 只有 4 卡,越界 if any(g < 8 for g in gpus_per_node): raise ValueError("节点 GPU 数不均,无法用单一 nproc_per_node") except ValueError as e: print("复现成功 ->", e) # 正确做法:每节点独立指定 correct = sum(gpus_per_node) print("每节点独立指定的总进程数:", correct) if __name__ == "__main__": main()

运行输出:

复现成功 -> 节点 GPU 数不均,无法用单一 nproc_per_node 每节点独立指定的总进程数: 12

统一假设在异构下失败,而"每节点独立指定"得到正确的 12,正是真实问题的抽象。

五、解决方案(第一层:最小直接修复)

最小且必须的一步:不用accelerate launch的统一启动,改用torchrun在每个节点分别指定自己的nproc_per_node。每个节点跑一条独立命令,accelerate 在脚本内照常初始化。

node0(8 卡):

# node0 torchrun \ --nnodes=2 \ --node_rank=0 \ --nproc_per_node=8 \ --master_addr=192.168.1.10 \ --master_port=29500 \ train.py

node1(4 卡):

# node1 torchrun \ --nnodes=2 \ --node_rank=1 \ --nproc_per_node=4 \ --master_addr=192.168.1.10 \ --master_port=29500 \ train.py

训练脚本train.py里照常:

# fix_layer1.py from accelerate import Accelerator acc = Accelerator() # torchrun 已设好 env,accelerate 自动接入

要点:

  • 每节点nproc_per_node独立设置,表达异构;
  • nnodes/node_rank/master_addr/master_port让 torchrun 组同一个训练;
  • accelerate 只负责训练侧,不负责启动,绕开统一 nproc 限制。

六、解决方案(第二层:结构性改进)

把异构启动做成可复用的启动器配置:用一个声明式配置描述"每节点可用 GPU 数",自动生成每条节点的 torchrun 命令,避免手敲出错:

# fix_layer2.py from dataclasses import dataclass from typing import List @dataclass(frozen=True) class NodeSpec: rank: int nproc: int @dataclass(frozen=True) class ClusterLaunch: master_addr: str master_port: int nodes: List[NodeSpec] def command_for(self, rank: int) -> str: node = next(n for n in self.nodes if n.rank == rank) n_nodes = len(self.nodes) return ( f"torchrun --nnodes={n_nodes} --node_rank={rank} " f"--nproc_per_node={node.nproc} " f"--master_addr={self.master_addr} --master_port={self.master_port} " f"train.py" ) # 用法:异构集群 cluster = ClusterLaunch( master_addr="192.168.1.10", master_port=29500, nodes=[NodeSpec(0, 8), NodeSpec(1, 4)], ) print(cluster.command_for(0)) # node0 起 8 进程 print(cluster.command_for(1)) # node1 起 4 进程

要点:

  • ClusterLaunchNodeSpec描述每节点进程数,生成对应 torchrun 命令;
  • 新增 / 调整节点只需改nodes列表,不必手敲每条命令;
  • 总进程数 =sum(n.nproc),异构天然支持。

七、解决方案(第三层:断言 / CI 守护)

写 pytest 验证"异构启动配置自洽":

# test_hetero_launch.py import pytest def total_processes(nodes): return sum(n.nproc for n in nodes) def test_hetero_total_correct(): nodes = [NodeSpec(0, 8), NodeSpec(1, 4)] assert total_processes(nodes) == 12 def test_each_node_independent_nproc(): nodes = [NodeSpec(0, 8), NodeSpec(1, 4)] procs = [n.nproc for n in nodes] assert procs[0] != procs[1], "异构节点进程数应不同" def test_node_rank_unique(): nodes = [NodeSpec(0, 8), NodeSpec(1, 4)] ranks = [n.rank for n in nodes] assert len(ranks) == len(set(ranks)), "node_rank 必须唯一"

把这类校验接进启动脚本,可防止"手敲命令时 nproc / node_rank 配错"。

八、排查清单

异构节点无法用 accelerate launch 时:

  1. 确认是否"每节点 GPU 数不同"导致统一nproc_per_node不成立;
  2. 放弃accelerate launch的统一启动,改用torchrun每节点独立指定;
  3. 每条节点命令的nnodes相同、node_rank不同、nproc_per_node各自填可用卡数;
  4. master_addr/master_port在所有节点一致;
  5. 训练脚本里仍用Accelerator(),torchrun 已设好环境变量;
  6. 用第六节的ClusterLaunch生成命令,避免手敲错;
  7. 把第七节的 pytest 接进启动脚本,校验节点配置自洽。

九、小结

异构节点(每节点 GPU 数不同)无法用accelerate launch,因为它用num_machines * nproc_per_node算总进程、隐含"每机同进程数"。真实集群常有异构,该假设不成立。解法是改用torchrun作为 per-node 启动器,每个节点独立指定自己的nproc_per_node,accelerate 只负责训练侧。

三层层级:

  • 第一层:放弃 accelerate launch 统一启动,改用 torchrun 每节点独立指定nproc_per_node
  • 第二层:用ClusterLaunch声明式描述每节点进程数,自动生成正确的 torchrun 命令;
  • 第三层:pytest 校验异构配置自洽(总进程数、node_rank 唯一),锁进启动脚本。

核心教训:任何"假设集群同构"的启动器,在异构集群上都会失效。把"每节点资源"作为一等公民(per-node 指定),比强行套统一参数稳健得多。torchrun 的 per-node 模型正是为此而生。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询