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

【Bug已解决】How to specify different number of process per node 解决方案
【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_nodetorchrun 是 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 # 异构时不成立 # 异构集群node08 卡node14 卡 # 无法用单一 nproc_per_node 表达 - ValueError根因链条accelerate launch用num_machines * nproc_per_node算总进程它要求所有节点nproc_per_node一致异构节点每节点 GPU 数不同不满足该假设要么报错、要么强行统一导致空闲卡浪费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(): # 异构node08 卡node14 卡 gpus_per_node [8, 4] try: # 强行用统一 nproc_per_node8 - 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 在脚本内照常初始化。node08 卡# node0 torchrun \ --nnodes2 \ --node_rank0 \ --nproc_per_node8 \ --master_addr192.168.1.10 \ --master_port29500 \ train.pynode14 卡# node1 torchrun \ --nnodes2 \ --node_rank1 \ --nproc_per_node4 \ --master_addr192.168.1.10 \ --master_port29500 \ train.py训练脚本train.py里照常# fix_layer1.py from accelerate import Accelerator acc Accelerator() # torchrun 已设好 envaccelerate 自动接入要点每节点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(frozenTrue) class NodeSpec: rank: int nproc: int dataclass(frozenTrue) 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 ( ftorchrun --nnodes{n_nodes} --node_rank{rank} f--nproc_per_node{node.nproc} f--master_addr{self.master_addr} --master_port{self.master_port} ftrain.py ) # 用法异构集群 cluster ClusterLaunch( master_addr192.168.1.10, master_port29500, nodes[NodeSpec(0, 8), NodeSpec(1, 4)], ) print(cluster.command_for(0)) # node0 起 8 进程 print(cluster.command_for(1)) # node1 起 4 进程要点ClusterLaunch用NodeSpec描述每节点进程数生成对应 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 时确认是否每节点 GPU 数不同导致统一nproc_per_node不成立放弃accelerate launch的统一启动改用torchrun每节点独立指定每条节点命令的nnodes相同、node_rank不同、nproc_per_node各自填可用卡数master_addr/master_port在所有节点一致训练脚本里仍用Accelerator()torchrun 已设好环境变量用第六节的ClusterLaunch生成命令避免手敲错把第七节的 pytest 接进启动脚本校验节点配置自洽。九、小结异构节点每节点 GPU 数不同无法用accelerate launch因为它用num_machines * nproc_per_node算总进程、隐含每机同进程数。真实集群常有异构该假设不成立。解法是改用torchrun作为 per-node 启动器每个节点独立指定自己的nproc_per_nodeaccelerate 只负责训练侧。三层层级第一层放弃 accelerate launch 统一启动改用 torchrun 每节点独立指定nproc_per_node第二层用ClusterLaunch声明式描述每节点进程数自动生成正确的 torchrun 命令第三层pytest 校验异构配置自洽总进程数、node_rank 唯一锁进启动脚本。核心教训任何假设集群同构的启动器在异构集群上都会失效。把每节点资源作为一等公民per-node 指定比强行套统一参数稳健得多。torchrun 的 per-node 模型正是为此而生。