SkyPilot 快速上手:用 PyTorch DDP 在云端启动 minGPT 分布式训练
【免费下载链接】skypilotThe AI Compute Platform for frontier teams. SkyPilot turns fragmented AI compute into one AI supercomputer, so frontier AI teams build custom intelligence faster.项目地址: https://gitcode.com/GitHub_Trending/sk/skypilot
本文以 SkyPilot 官方的 Quickstart 教程(docs/source/getting-started/tutorial.rst)为主线,带你从零跑通一个完整的分布式训练任务:使用 PyTorch 的 Distributed Data Parallel(DDP)训练一个受 Karpathy minGPT 启发的 GPT 类语言模型。你将掌握通过CLI(sky launch)与Python SDK(sky.Task)两种方式定义任务、提交训练、查看日志、优雅地取消/分离作业,以及用num_nodes与 SkyPilot 内置环境变量一键把单机训练扩展到多节点。文末还会结合仓库源码剖析这些内置环境变量(SKYPILOT_NUM_NODES、SKYPILOT_NODE_IPS、SKYPILOT_NUM_GPUS_PER_NODE、SKYPILOT_NODE_RANK)的生成原理,帮助你理解 SkyPilot 分布式执行(gang scheduling)背后的机制。
任务背景:minGPT + DDP
示例任务训练的是一个 GPT 风格模型(灵感来自 Karpathy 的 minGPT),训练框架采用 PyTorch 官方的distributed/minGPT-ddp示例,训练方式为 DDP(Distributed Data Parallel)。DDP 的核心思想是:每个进程持有模型的一个副本,各自在本地 GPU 上处理不同 batch 的数据,通过梯度同步(AllReduce)保持模型参数一致。
在 SkyPilot 中,你不需要手动去各云厂商控制台创建虚拟机、配置 SSH、安装环境,只需要用一段 YAML(或几行 Python)描述"要什么资源"、"怎么装环境"、"跑什么命令",剩下的调度与交付由 SkyPilot 完成。
本教程对应的可运行示例也收录在仓库的 examples/distributed-pytorch/train.yaml 与 examples/distributed-pytorch/sdk_scripts/train.py 中,可直接对照阅读。
方式一:使用 CLI + SkyPilot YAML
SkyPilot 提供了一个直观的 YAML 接口,用于描述集群、作业或服务的全部要素:资源需求、setup 命令、run 命令、文件挂载、存储挂载等。YAML 规范中的所有字段都是可选的,未指定时使用默认值,你只需要写与任务相关的字段(完整字段说明见 docs/source/reference/yaml-spec.rst)。
编写 train.yaml
# train.yaml name: minGPT-ddp resources: cpus: 4+ accelerators: L4:4 # Or A100:8, H100:8 # Optional: upload a working directory to remote ~/sky_workdir. # Commands in "setup" and "run" will be executed under it. # # workdir: . # Optional: upload local files. # Format: # /remote/path: /local/path # # file_mounts: # ~/.vimrc: ~/.vimrc # ~/.netrc: ~/.netrc setup: | git clone --depth 1 https://github.com/pytorch/examples || true cd examples git filter-branch --prune-empty --subdirectory-filter distributed/minGPT-ddp uv pip install --system -r requirements.txt run: | cd examples/mingpt export LOGLEVEL=INFO echo "Starting minGPT-ddp training" torchrun \ --nproc_per_node=$SKYPILOT_NUM_GPUS_PER_NODE \ main.py逐段解读这个文件:
name:任务名,用于展示与区分任务。resources:硬件需求。cpus: 4+表示至少 4 个 vCPU;accelerators: L4:4表示每节点 4 张 NVIDIA L4 GPU(也可换成A100:8、H100:8等,格式为型号:数量)。SkyPilot 会自动在可用云中寻找满足该需求的最便宜实例完成交付。workdir(可选,默认注释):把本地目录整体上传到远端~/sky_workdir,setup与run中的命令都会在该目录下执行。file_mounts(可选,默认注释):将本地文件挂载到远端,格式为远端路径: 本地路径,例如把~/.vimrc、~/.netrc带过去。setup:一次性初始化命令,在每个节点上执行。这里克隆 PyTorch 官方示例仓库,并用git filter-branch抽取其中的distributed/minGPT-ddp子目录,然后安装依赖。|| true保证仓库已存在时克隆失败也不会中断流程。run:作业运行时执行的命令。通过torchrun启动 DDP 训练,其中--nproc_per_node=$SKYPILOT_NUM_GPUS_PER_NODE让每个节点上的进程数自动等于该节点被分配的 GPU 数——这正是 SkyPilot 自动注入的分布式训练环境变量,详见下文"环境变量"一节。
提示:
workdir和file_mounts在示例中是注释掉的。要学习如何用它们挂载本地目录/文件或对象存储桶(S3、GCS、R2),可参考 docs/source/examples/syncing-code-artifacts.rst。
提示:
SKYPILOT_NUM_GPUS_PER_NODE环境变量由 SkyPilot 自动设置为每个节点的 GPU 数量,完整环境变量列表见 docs/source/running-jobs/environment-variables.rst。
启动训练
$ sky launch -c mingpt train.yaml-c mingpt:为集群命名,之后对同一集群的复用(如sky exec)都靠这个名字。- SkyPilot 会完成:交付资源 → 执行 setup → 执行 run的完整流程,并且优先选择满足资源需求的最便宜集群(这是 SkyPilot 的云优选器 core 能力)。
方式二:使用 Python SDK
如果你更习惯在 Python 中以编程方式驱动训练,SkyPilot 提供了一等公民的 Python SDK(接口参考见 docs/source/reference/pythonapi.rst)。
编写 train.py
# train.py import sky minGPT_ddp_task = sky.Task( name='minGPT-ddp', resources=sky.Resources( cpus='4+', accelerators='L4:4', ), # Optional: upload a working directory to remote ~/sky_workdir. # Commands in "setup" and "run" will be executed under it. # # workdir='.', # # Optional: upload local files. # Format: # /remote/path: /local/path # # file_mounts={ # '~/.vimrc': '~/.vimrc', # '~/.netrc': '~/.netrc', # }, setup=[ 'git clone --depth 1 https://github.com/pytorch/examples || true', 'cd examples', 'git filter-branch --prune-empty --subdirectory-filter distributed/minGPT-ddp', 'uv pip install --system -r requirements.txt', ], run=[ 'cd examples/mingpt', 'export LOGLEVEL=INFO', 'torchrun --nproc_per_node=$SKYPILOT_NUM_GPUS_PER_NODE main.py', ] ) cluster_name = 'mingpt' launch_request = sky.launch(task=minGPT_ddp_task, cluster_name=cluster_name) job_id, _ = sky.stream_and_get(launch_request) sky.tail_logs(cluster_name, job_id, follow=True)与 YAML 写法一一对应:
sky.Task(...):等价于 YAML 中的顶层任务描述。sky.Resources(cpus='4+', accelerators='L4:4'):等价于resources字段。setup=[...]/run=[...]:与 YAML 的setup/run对应,传字符串列表即可。sky.launch(task=..., cluster_name=...):发起启动请求,等价于sky launch -c mingpt train.yaml。sky.stream_and_get(launch_request):等待任务提交完成并返回(job_id, handle)。sky.tail_logs(cluster_name, job_id, follow=True):实时跟踪远端训练日志。
运行
$ python train.py仓库中的 examples/distributed-pytorch/sdk_scripts/train.py 在训练结束后还演示了如何用scp下载gpt_snapshot.pt检查点,并通过sky.down(cluster_name)释放集群,非常适合作为"训练 → 取产物 → 销毁"完整闭环的参考。
作业生命周期:分离、取消与取回产物
任务提交后,SkyPilot 会交付满足资源需求的最便宜集群、执行 setup、再执行 run。有几个与作业生命周期相关的实用点:
- 安全地分离:训练开始后,你可以放心地按下
Ctrl-C断开日志输出,任务仍会在远端集群上继续运行。 - 停止作业:如需停止,使用
sky cancel <cluster_name> <job_id>(CLI 完整参考见 docs/source/reference/cli.rst)。 - 取回产物:训练结束后,用熟悉的工具(如
scp、rsync,或文件挂载)把日志、检查点等产物取回本地,参考 docs/source/examples/syncing-code-artifacts.rst。
你可以放心地把上面的 YAML 或 Python 代码复制过去,针对自己的项目做定制。
扩展到多节点:加一个 num_nodes 即可
把单机训练扩展到多机集群,只需要在任务中加一行num_nodes,并把run命令升级为 torchrun 的多节点形态。
在 YAML 中扩展
resources: cpus: 4+ accelerators: L4:4 # Use 2 nodes with 4 GPUs each (8 GPUs total) num_nodes: 2对应的run更新为 torchrun 的多节点参数:
run: | cd examples source .venv/bin/activate cd mingpt export LOGLEVEL=INFO MASTER_ADDR=$(echo "$SKYPILOT_NODE_IPS" | head -n1) echo "Starting distributed training, head node: $MASTER_ADDR" torchrun \ --nnodes=$SKYPILOT_NUM_NODES \ --nproc_per_node=$SKYPILOT_NUM_GPUS_PER_NODE \ --master_addr=$MASTER_ADDR \ --master_port=8008 \ --node_rank=${SKYPILOT_NODE_RANK} \ main.py要点说明:
num_nodes: 2表示任务运行在 2 个节点上,每个节点 4 张 L4(共 8 张 GPU)。MASTER_ADDR=$(echo "$SKYPILOT_NODE_IPS" | head -n1):从 SkyPilot 注入的节点 IP 列表中取出第一行作为 torchrun 的 master 地址。--master_port=8008:master 节点上用于分布式通信的端口。- 使用
uv venv/source .venv/bin/activate管理虚拟环境时,记得在 run 命令中先激活(完整多节点版见 examples/distributed-pytorch/train.yaml)。
SkyPilot 自动注入的分布式环境变量
以下环境变量由 SkyPilot 自动为分布式训练设置,你无需手动配置:
SKYPILOT_NUM_NODES:集群/任务的总节点数。SKYPILOT_NUM_GPUS_PER_NODE:每个节点上的 GPU 数量。SKYPILOT_NODE_RANK:当前节点的 rank(从 0 开始)。SKYPILOT_NODE_IPS:所有节点的 IP 地址列表,每行一个。
完整环境变量参考见 docs/source/running-jobs/environment-variables.rst。
用 CLI 直接指定资源来扩展
除了修改 YAML,sky launch也支持在命令行直接覆盖资源需求完成扩容。例如仓库 examples/distributed-pytorch/README.md 中的做法——在同一个 YAML 上直接指定 4 个节点、每节点 4 张 L4,同时把 CPU 提到 8 核以上以避免 CPU 成为瓶颈:
sky launch -c train train.yaml --num-nodes 4 --gpus L4:4 --cpus 8+环境变量的底层原理:从源码看 SKYPILOT_* 是如何注入的
理解这些内置环境变量的来源,能帮你更自信地编写分布式启动脚本。相关实现集中在 sky/skylet/constants.py 与 sky/backends/task_codegen.py。
变量名的定义
在 sky/skylet/constants.py 中可以看到这些变量的统一定义,它们统一以SKYPILOT_前缀(SKYPILOT_ENV_VAR_PREFIX = 'SKYPILOT_')开头:
SKYPILOT_NUM_NODES = f'{SKYPILOT_ENV_VAR_PREFIX}NUM_NODES' SKYPILOT_NODE_IPS = f'{SKYPILOT_ENV_VAR_PREFIX}NODE_IPS' SKYPILOT_SETUP_NUM_GPUS_PER_NODE = ( f'{SKYPILOT_ENV_VAR_PREFIX}SETUP_NUM_GPUS_PER_NODE') SKYPILOT_NUM_GPUS_PER_NODE = f'{SKYPILOT_ENV_VAR_PREFIX}NUM_GPUS_PER_NODE' SKYPILOT_NODE_RANK = f'{SKYPILOT_ENV_VAR_PREFIX}NODE_RANK'注意还有一个SKYPILOT_SETUP_NUM_GPUS_PER_NODE——它在 setup 阶段注入,供 setup 命令获知每节点 GPU 数。
注入流程:Ray placement group + 运行时求值
在 sky/backends/task_codegen.py 的_add_ray_task中可以看到完整的注入逻辑:
收集节点 IP 与总节点数(task_codegen.py#L630-L631):通过 Ray 的 placement group 做 gang scheduling(
STRICT_SPREAD,见 task_codegen.py#L464-L466),先在各节点上探测ray.util.get_node_ip_address(),得到节点 IP 排序列表后,写入:SKYPILOT_NODE_IPS = job_ip_list_str('\n'.join(job_ip_rank_list),每行一个 IP)SKYPILOT_NUM_NODES = len(job_ip_rank_list)
按节点注入 GPU 数与 rank(task_codegen.py#L654-L670):每个节点的 Ray task 在运行时,依据
gang_scheduling_id得到该节点 IP,再由 IP 映射到全局 rank,从而设置:SKYPILOT_NUM_GPUS_PER_NODE = int(math.ceil(num_gpus))(向上取整,因此即使accelerators写了 GPU 小数份额也能得到正确的整数)SKYPILOT_NODE_RANK = rank(0、1、2……)
随任务脚本一起下发:最终这些变量被塞进
sky_env_vars_dict,通过run_bash_command_with_log_and_return_pid.remote(..., env_vars=sky_env_vars_dict, ...)注入到每个节点执行 run 命令的进程中。
这正是 docs/source/running-jobs/distributed-jobs.rst 所描述语义的代码级印证:
SKYPILOT_NODE_RANK:执行当前作业的节点 rank(0 到num_nodes-1);SKYPILOT_NODE_IPS:为作业保留的节点 IP 字符串,每行一个;SKYPILOT_NUM_NODES:为作业保留的节点数,等于echo "$SKYPILOT_NODE_IPS" | wc -l;SKYPILOT_NUM_GPUS_PER_NODE:每节点为作业保留的 GPU 数,等于accelerators: <name>:<count>中的 count(小数向上取整)。
此外,setup阶段执行时也会注入SKYPILOT_NUM_NODES(见 task_codegen.py#L479),所以在 setup 脚本中同样可以读取到集群规模信息。
为什么分布式训练能"自动"工作
由于这些变量是在集群已经交付、节点 IP 与 rank 确定之后由 SkyPilot 在运行时计算并注入的,你的run脚本只需要像普通 shell 一样读取它们,就能获得正确的节点拓扑信息——无需手工维护 hostfile、无需预先知道 master 地址,也无需关心云厂商的 IP 分配方式。这也就是为什么上面的多节点 torchrun 命令可以原样拷贝到任意云(AWS、GCP、Azure、Kubernetes 等)上运行。
更多进阶参考
- 想要更深入理解多节点作业,可阅读 docs/source/running-jobs/distributed-jobs.rst,其中还包含大节点数(如 100 节点)场景下
[Errno 24] Too many open files的解决建议(ulimit -n 65535)。 - torchrun 的另一个常用后端是
rdzv(rendezvous):它自动处理各节点的 rank,示例见 examples/distributed-pytorch/train-rdzv.yaml(使用--rdzv_backend=c10d --rdzv_endpoint=$MASTER_ADDR:29500 --rdzv_id $SKYPILOT_TASK_ID),对应 SDK 版本见 examples/distributed-pytorch/sdk_scripts/train_rdzv.py。 - SkyPilot 的环境变量远不止这 4 个,完整的
SKYPILOT_*变量清单(含SKYPILOT_TASK_ID、SKYPILOT_CLUSTER_INFO等)见 docs/source/running-jobs/environment-variables.rst。 - 关于 YAML 的完整字段(
volumes、envs、secrets、job_recovery、any_of/ordered多集群选择等),见 docs/source/reference/yaml-spec.rst。
至此,你已经掌握了用 SkyPilot 以 CLI 与 Python SDK 两种方式提交 PyTorch DDP 分布式训练、动态扩展多节点、读取内置分布式环境变量,并理解其底层注入原理的完整链路。接下来,直接复制本文的 YAML/Python 示例,把任务名、资源与 setup 命令替换成你自己的模型与依赖,即可在云端跑起属于自己的大规模训练。
【免费下载链接】skypilotThe AI Compute Platform for frontier teams. SkyPilot turns fragmented AI compute into one AI supercomputer, so frontier AI teams build custom intelligence faster.项目地址: https://gitcode.com/GitHub_Trending/sk/skypilot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考