torchrun(弹性启动)¶
torch.distributed.launch
的超集。
torchrun
提供了与 torch.distributed.launch
相同的功能超集,并具有以下附加功能
工作器故障由重新启动所有工作器优雅地处理。
工作器
RANK
和WORLD_SIZE
被自动分配。允许在最小和最大大小之间更改节点数量(弹性)。
注意
torchrun
是一个指向主模块 torch.distributed.run 的 Python 控制台脚本,该模块在 setup.py 中的 entry_points
配置中声明。它等效于调用 python -m torch.distributed.run
。
从 torch.distributed.launch 迁移到 torchrun¶
torchrun
支持与 torch.distributed.launch
相同的参数,**除了** --use-env
,它现在已弃用。要从 torch.distributed.launch
迁移到 torchrun
,请执行以下步骤
如果您的训练脚本已从
LOCAL_RANK
环境变量中读取local_rank
。那么您只需省略--use-env
标志,例如:torch.distributed.launch
torchrun
$ python -m torch.distributed.launch --use-env train_script.py
$ torchrun train_script.py
如果您的训练脚本从
--local-rank
cmd 参数中读取本地排名。将您的训练脚本更改为从LOCAL_RANK
环境变量中读取,如下面的代码片段所示torch.distributed.launch
torchrun
import argparse parser = argparse.ArgumentParser() parser.add_argument("--local-rank", type=int) args = parser.parse_args() local_rank = args.local_rank
import os local_rank = int(os.environ["LOCAL_RANK"])
在版本 2.0.0 中更改: 启动器将传递 --local-rank=<rank>
参数到您的脚本。从 PyTorch 2.0.0 开始,带破折号的 --local-rank
比以前使用的带下划线的 --local_rank
更受欢迎。
为了向后兼容,用户可能需要在他们的参数解析代码中处理这两种情况。这意味着在参数解析器中包含 "--local-rank"
和 "--local_rank"
。如果只提供 "--local_rank"
,启动器将触发错误:“错误:无法识别的参数:–local-rank=<rank>”。对于只支持 PyTorch 2.0.0+ 的训练代码,包含 "--local-rank"
应该就足够了。
>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument("--local-rank", "--local_rank", type=int)
>>> args = parser.parse_args()
上述更改足以从 torch.distributed.launch
迁移到 torchrun
。要利用 torchrun
的新功能,例如弹性、容错和错误报告,请参阅
训练脚本 了解有关编写与
torchrun
兼容的训练脚本的更多信息。本页的其余部分,了解有关
torchrun
功能的更多信息。
用法¶
单节点多工作器¶
torchrun
--standalone
--nnodes=1
--nproc-per-node=$NUM_TRAINERS
YOUR_TRAINING_SCRIPT.py (--arg1 ... train script args...)
堆叠式单节点多工作器¶
要在同一主机上运行单节点多工作器的多个实例(单独的作业),我们需要确保每个实例(作业)都设置在不同的端口上,以避免端口冲突(或更糟的是,两个作业合并为一个作业)。为此,您必须使用 --rdzv-backend=c10d
运行,并通过设置 --rdzv-endpoint=localhost:$PORT_k
指定不同的端口。对于 --nodes=1
,让 torchrun
自动选择一个空闲的随机端口通常比手动为每次运行分配不同的端口更方便。
torchrun
--rdzv-backend=c10d
--rdzv-endpoint=localhost:0
--nnodes=1
--nproc-per-node=$NUM_TRAINERS
YOUR_TRAINING_SCRIPT.py (--arg1 ... train script args...)
容错(固定大小的 worker 数,没有弹性,容忍 3 个故障)¶
torchrun
--nnodes=$NUM_NODES
--nproc-per-node=$NUM_TRAINERS
--max-restarts=3
--rdzv-id=$JOB_ID
--rdzv-backend=c10d
--rdzv-endpoint=$HOST_NODE_ADDR
YOUR_TRAINING_SCRIPT.py (--arg1 ... train script args...)
HOST_NODE_ADDR
,格式为 <主机>[:<端口>](例如 node1.example.com:29400),指定节点和 C10d rendezvous 后端应实例化和托管的端口。它可以是您训练集群中的任何节点,但理想情况下,您应该选择一个带宽高的节点。
注意
如果没有指定端口号,HOST_NODE_ADDR
默认值为 29400。
弹性(min=1
,max=4
,容忍最多 3 个成员更改或故障)¶
torchrun
--nnodes=1:4
--nproc-per-node=$NUM_TRAINERS
--max-restarts=3
--rdzv-id=$JOB_ID
--rdzv-backend=c10d
--rdzv-endpoint=$HOST_NODE_ADDR
YOUR_TRAINING_SCRIPT.py (--arg1 ... train script args...)
HOST_NODE_ADDR
,格式为 <主机>[:<端口>](例如 node1.example.com:29400),指定节点和 C10d rendezvous 后端应实例化和托管的端口。它可以是您训练集群中的任何节点,但理想情况下,您应该选择一个带宽高的节点。
注意
如果没有指定端口号,HOST_NODE_ADDR
默认值为 29400。
关于 rendezvous 后端¶
对于多节点训练,您需要指定
--rdzv-id
:一个唯一的作业 ID(由参与作业的所有节点共享)--rdzv-backend
:torch.distributed.elastic.rendezvous.RendezvousHandler
的实现--rdzv-endpoint
:rendezvous 后端运行的端点;通常为host:port
格式。
目前,c10d
(推荐)、etcd-v2
和 etcd
(旧版)rendezvous 后端开箱即用。要使用 etcd-v2
或 etcd
,请设置一个启用了 v2
API 的 etcd 服务器(例如 --enable-v2
)。
警告
etcd-v2
和 etcd
rendezvous 使用 etcd API v2。您必须在 etcd 服务器上启用 v2 API。我们的测试使用 etcd v3.4.3。
警告
对于基于 etcd 的 rendezvous,我们建议使用 etcd-v2
而不是 etcd
,两者在功能上等效,但使用的是经过修改的实现。 etcd
处于维护模式,将在未来版本中移除。
定义¶
Node
- 物理实例或容器;映射到作业管理器使用的单元。Worker
- 分布式训练中的 worker。WorkerGroup
- 执行相同函数(例如训练器)的 worker 集。LocalWorkerGroup
- 运行在同一节点上的 worker 组中的 worker 子集。RANK
- worker 在 worker 组中的排名。WORLD_SIZE
- worker 组中 worker 的总数。LOCAL_RANK
- worker 在本地 worker 组中的排名。LOCAL_WORLD_SIZE
- 本地 worker 组的大小。rdzv_id
- 用户定义的 ID,唯一标识作业的 worker 组。每个节点使用此 ID 作为特定 worker 组的成员加入。
rdzv_backend
- rendezvous 的后端(例如c10d
)。这通常是一个强一致的键值存储。rdzv_endpoint
- rendezvous 后端端点;通常为<host>:<port>
格式。
一个 Node
运行 LOCAL_WORLD_SIZE
个 worker,这些 worker 构成一个 LocalWorkerGroup
。作业中所有节点的 LocalWorkerGroups
的并集构成 WorkerGroup
。
环境变量¶
以下环境变量在您的脚本中可用
LOCAL_RANK
- 本地排名。RANK
- 全局排名。GROUP_RANK
- worker 组的排名。一个介于 0 和max_nnodes
之间的数字。当每个节点运行一个 worker 组时,这是节点的排名。ROLE_RANK
- 所有具有相同角色的 worker 中的 worker 的排名。worker 的角色在WorkerSpec
中指定。LOCAL_WORLD_SIZE
- 本地世界大小(例如本地运行的 worker 数);等于在torchrun
上指定的--nproc-per-node
。WORLD_SIZE
- 世界大小(作业中 worker 的总数)。ROLE_WORLD_SIZE
- 使用在WorkerSpec
中指定的相同角色启动的 worker 的总数。MASTER_ADDR
- 运行排名为 0 的 worker 的主机的 FQDN;用于初始化 Torch 分布式后端。MASTER_PORT
-MASTER_ADDR
上的端口,可用于托管 C10d TCP 存储。TORCHELASTIC_RESTART_COUNT
- 到目前为止的 worker 组重启次数。TORCHELASTIC_MAX_RESTARTS
- 配置的最大重启次数。TORCHELASTIC_RUN_ID
- 等于 rendezvousrun_id
(例如唯一的作业 ID)。PYTHON_EXEC
- 系统可执行文件覆盖。如果提供,python 用户脚本将使用PYTHON_EXEC
的值作为可执行文件。默认情况下使用 sys.executable。
部署¶
(C10d 后端不需要)启动 rendezvous 后端服务器并获取端点(作为
--rdzv-endpoint
传递给启动器脚本)单节点多 worker:在主机上启动启动器以启动代理进程,该进程创建并监控本地 worker 组。
多节点多 worker:在参与训练的所有节点上以相同的参数启动启动器。
当使用作业/集群管理器时,多节点作业的入口点命令应该是此启动器。
故障模式¶
worker 故障:对于具有
n
个 worker 的训练作业,如果k<=n
个 worker 失败,所有 worker 将停止并重启,最多重启max_restarts
次。代理故障:代理故障会导致本地 worker 组故障。由作业管理器决定是使整个作业失败(组语义)还是尝试替换节点。代理都支持这两种行为。
节点故障:与代理故障相同。
成员更改¶
节点离开(缩容):通知代理节点离开,停止所有现有 worker,形成新的
WorkerGroup
,并使用新的RANK
和WORLD_SIZE
启动所有 worker。节点到达(扩容):新节点被接纳到作业中,停止所有现有 worker,形成新的
WorkerGroup
,并使用新的RANK
和WORLD_SIZE
启动所有 worker。
重要通知¶
此实用程序和多进程分布式(单节点或多节点)GPU 训练目前仅使用 NCCL 分布式后端才能获得最佳性能。因此,NCCL 后端是 GPU 训练的推荐后端。
初始化 Torch 进程组所需的 环境变量 由此模块提供,您无需手动传递
RANK
。要在训练脚本中初始化进程组,只需运行
>>> import torch.distributed as dist
>>> dist.init_process_group(backend="gloo|nccl")
在您的训练程序中,您可以使用常规的分布式函数,也可以使用
torch.nn.parallel.DistributedDataParallel()
模块。如果您的训练程序使用 GPU 进行训练,并且您想使用torch.nn.parallel.DistributedDataParallel()
模块,以下是配置方法。
local_rank = int(os.environ["LOCAL_RANK"])
model = torch.nn.parallel.DistributedDataParallel(model,
device_ids=[local_rank],
output_device=local_rank)
请确保 device_ids
参数设置为代码将运行的唯一 GPU 设备 ID。这通常是进程的本地排名。换句话说,device_ids
需要为 [int(os.environ("LOCAL_RANK"))]
,output_device
需要为 int(os.environ("LOCAL_RANK"))
才能使用此实用程序
在发生故障或成员更改时,所有幸存的 worker 会立即被终止。确保将进度保存到检查点。检查点的频率应取决于您的作业对丢失工作的容忍度。
此模块仅支持同构
LOCAL_WORLD_SIZE
。也就是说,假定所有节点都运行相同数量的本地 worker(每个角色)。RANK
不稳定。重启之间,节点上的本地工作器可能会被分配到与之前不同的秩范围。切勿对秩的稳定性或RANK
和LOCAL_RANK
之间的一些相关性进行硬编码假设。使用弹性 (
min_size!=max_size
) 时,切勿对WORLD_SIZE
进行硬编码假设,因为世界大小可能会随着节点被允许离开和加入而改变。建议您的脚本具有以下结构
def main():
load_checkpoint(checkpoint_path)
initialize()
train()
def train():
for batch in iter(dataset):
train_step(batch)
if should_checkpoint:
save_checkpoint(checkpoint_path)
(推荐) 在工作器错误时,此工具将汇总错误的详细信息(例如时间、秩、主机、PID、回溯等)。在每个节点上,第一个错误(按时间戳)将被启发式地报告为“根本原因”错误。为了将回溯作为此错误摘要输出的一部分,您必须使用以下示例所示的方法装饰训练脚本中的主要入口点函数。如果没有装饰,则摘要将不包含异常的回溯,并且只包含退出码。有关 Torchelastic 错误处理的详细信息,请参阅:https://pytorch.ac.cn/docs/stable/elastic/errors.html
from torch.distributed.elastic.multiprocessing.errors import record
@record
def main():
# do train
pass
if __name__ == "__main__":
main()