在 Amazon EKS 上用 NVRx 构建可容错的 PyTorch 分布式训练

2026-09-17 29 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

GPU 训练失败并不一定意味着整个任务要从头开始。将 NVIDIA Resiliency Extension(NVRx)接入运行在 Amazon EKS 上的 PyTorch FSDP 训练后,可以把故障恢复从“重建整个 Job、重新加载全部状态”缩短到秒级,同时通过异步 checkpoint 把存储 I/O 与训练计算重叠起来。

这套方案的核心不只是保存模型,而是把训练任务设计成可以在进程内或作业内恢复:正常运行时持续产生可恢复的 checkpoint;发生 GPU 故障时,优先尝试 in-process restart;如果进程无法继续,再由 ft_launcher 在同一个训练 Job 内重启训练进程。

三个能力分别解决什么问题

异步 checkpoint:不要让保存动作卡住训练

传统 checkpoint 通常由训练主线程执行:收集 FSDP 状态、序列化、写入共享存储,整个过程中 GPU 可能处于等待状态。模型和优化器状态越大,这个停顿越明显。

异步 checkpoint 的思路是把“生成可保存状态”和“写入持久化存储”拆开。训练进程继续推进下一个 step,后台线程或进程负责把 checkpoint 写入 Amazon S3、FSx for Lustre 或其他持久化存储。实际部署时,需要确认底层存储具备足够的吞吐,并避免多个 worker 同时写入同一个目标路径。

一个实用的 checkpoint 目录可以按训练任务、全局 step 和版本组织:

s3://my-training-checkpoints/
└── job-20250301/
    ├── step-000100/
    ├── step-000200/
    ├── step-000300/
    └── latest.json

latest.json 只应该在一个完整 checkpoint 写入成功后更新。这样即使节点在写盘期间崩溃,恢复逻辑也不会误选到半成品目录。

In-process restart:优先在当前 Job 内恢复

如果故障可以被训练进程和 NVRx 的故障处理逻辑捕获,in-process restart 可以避免重新申请 Kubernetes Job。恢复时通常需要完成以下动作:

  1. 停止或隔离失效的通信组;
  2. 清理旧的 CUDA、NCCL 和分布式状态;
  3. 从最近的完整 checkpoint 恢复模型、优化器、学习率调度器和随机数状态;
  4. 重新建立 FSDP 和 distributed process group;
  5. 从保存的全局 step 继续训练。

恢复粒度取决于 checkpoint 中保存了什么。如果只保存模型权重而没有保存 optimizer、scheduler、随机数和数据迭代器状态,任务可能可以继续,但训练轨迹和严格可复现性会发生变化。

ft_launcher:进程无法自救时,在 Job 内重启

更严重的 GPU 故障可能会让 Python 进程直接退出。这时可以使用 NVRx 的 ft_launcher 管理训练入口,在同一个 Kubernetes Job 中重新启动训练进程。它的价值在于把“训练进程失败”和“训练任务永久失败”分开处理。

容器入口不要直接调用训练脚本,而是让 launcher 成为主进程。下面的命令示例假设镜像中已经安装了 NVRx,并且训练脚本位于 /workspace/train.py

#!/usr/bin/env bash
set -euo pipefail

export MASTER_ADDR="${MASTER_ADDR:?MASTER_ADDR is required}"
export MASTER_PORT="${MASTER_PORT:-29500}"
export CHECKPOINT_DIR="${CHECKPOINT_DIR:-/shared/checkpoints/job-20250301}"

# 参数名可能随 NVRx 版本变化;请以镜像中 ft_launcher --help 为准。
exec ft_launcher \
  --nproc-per-node="${NPROC_PER_NODE:-8}" \
  --max-restarts="${MAX_RESTARTS:-10}" \
  /workspace/train.py \
  --config /workspace/configs/fsdp.yaml \
  --checkpoint-dir "$CHECKPOINT_DIR"

运行前建议在目标镜像中确认入口和参数:

python -m pip show nvidia-resiliency-ext
ft_launcher --help

上面的 launcher 参数是部署示例,不应直接假定适用于所有 NVRx 版本。升级 NVRx 或基础镜像时,应把 --help 输出纳入集成测试,避免参数变化导致 Pod 启动即失败。

在 EKS 中安排训练 Pod

Kubernetes 层需要解决两个问题:让多个 Pod 能够互相发现并建立分布式通信,以及让 checkpoint 在 Pod 重建后仍然可见。下面是一个可改造的 StatefulSet 示例。它使用 headless Service 提供稳定 DNS,并把共享 checkpoint 路径挂载到 /shared/checkpoints

apiVersion: v1
kind: Service
metadata:
  name: fsdp-train
spec:
  clusterIP: None
  selector:
    app: fsdp-train
  ports:
    - name: master
      port: 29500
      targetPort: 29500
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: fsdp-train
spec:
  serviceName: fsdp-train
  replicas: 2
  selector:
    matchLabels:
      app: fsdp-train
  template:
    metadata:
      labels:
        app: fsdp-train
    spec:
      restartPolicy: Always
      containers:
        - name: trainer
          image: <account>.dkr.ecr.<region>.amazonaws.com/fsdp-nvrx:latest
          command: ["/bin/bash", "/workspace/entrypoint.sh"]
          env:
            - name: MASTER_ADDR
              value: "fsdp-train-0.fsdp-train"
            - name: MASTER_PORT
              value: "29500"
            - name: NNODES
              value: "2"
            - name: NODE_RANK
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: CHECKPOINT_DIR
              value: "/shared/checkpoints/job-20250301"
          resources:
            requests:
              nvidia.com/gpu: "8"
            limits:
              nvidia.com/gpu: "8"
          volumeMounts:
            - name: checkpoints
              mountPath: /shared/checkpoints
      volumes:
        - name: checkpoints
          persistentVolumeClaim:
            claimName: fsdp-checkpoints

示例中的 NODE_RANK 仍需要在 entrypoint 中从 StatefulSet Pod 名称解析为整数,或改用 Job/自定义控制器注入 rank。真实环境还需要补充 GPU 节点选择器、Topology Manager、EFA/NCCL 配置、Pod 反亲和性以及适合训练规模的存储类。若 checkpoint 使用 S3,则可以把写入逻辑放在训练代码中,并通过 IAM Roles for Service Accounts(IRSA)授予最小权限,而不是把 AWS 密钥放进 Secret。

FSDP checkpoint 设计要点

FSDP 会把参数和梯度分片到不同 rank。checkpoint 设计必须明确保存格式和恢复方式:

  • 需要快速恢复训练时,保存模型、optimizer、scheduler、全局 step 和必要的随机数状态;
  • 使用与 FSDP 配置匹配的 state-dict 类型,避免保存端和恢复端的分片语义不一致;
  • 由 rank 0 或专门的 checkpoint 协调逻辑负责提交最终 manifest;
  • 写临时目录,完成后再原子地标记为可用;
  • 清理旧 checkpoint,但至少保留两个可恢复版本,防止最新版本损坏。

下面是一个与框架无关的恢复流程示例。它不是 NVRx 的固定 API,而是训练代码应具备的最小控制结构:

from pathlib import Path
import torch


def find_latest_checkpoint(root: str) -> Path | None:
    candidates = sorted(Path(root).glob("step-*/READY"))
    return candidates[-1].parent if candidates else None


def restore_training_state(model, optimizer, scheduler, root: str) -> int:
    checkpoint = find_latest_checkpoint(root)
    if checkpoint is None:
        return 0

    state = torch.load(checkpoint / "training_state.pt", map_location="cpu")
    model.load_state_dict(state["model"])
    optimizer.load_state_dict(state["optimizer"])
    scheduler.load_state_dict(state["scheduler"])
    return int(state["global_step"])

生产代码需要根据 FSDP 的 sharded state-dict 配置替换 load_state_dict 细节,并处理 rank 间同步。这里刻意使用 READY 标记,避免恢复逻辑把未完成的异步写入当成有效 checkpoint。

故障注入比“成功跑完”更重要

训练效率不能只看无故障时的 samples/sec。应该至少覆盖以下测试:

# 查看训练 Pod 与 GPU 状态
kubectl get pods -l app=fsdp-train -o wide
kubectl logs -f statefulset/fsdp-train -c trainer

# 在测试集群中模拟一个训练 Pod 被删除
kubectl delete pod fsdp-train-1

# 检查 Pod 是否重新加入,以及是否从最近 checkpoint 继续
kubectl get pods -l app=fsdp-train -w
kubectl logs fsdp-train-1 -c trainer | grep -E "restart|checkpoint|global_step"

测试指标至少包括:恢复耗时、丢失的训练 step、checkpoint 写入期间的吞吐、重启次数、最终 loss 曲线是否异常,以及失败后是否留下需要人工清理的 NCCL 或临时资源。对于 H100 集群,摘要中的 2 到 8 节点基准显示,在合理的异步 checkpoint 和恢复配置下,训练效率可以达到 99% 以上,恢复时间达到秒级;但这个结果不应直接外推到所有网络、存储和模型规模。

落地时的检查清单

  • 先在单节点上验证 FSDP checkpoint 的保存和恢复,再扩展到多节点;
  • 固定并记录 PyTorch、CUDA、NCCL、NVRx、GPU 驱动和容器版本;
  • 使用 ft_launcher --help 验证当前镜像支持的参数,不复制其他版本的命令行;
  • 确认 checkpoint 存储在 Pod 重启后仍可访问,并具备足够的 I/O 吞吐;
  • 让 checkpoint 具备完整性标记和保留策略;
  • 通过故障注入验证 in-process restart 和 ft_launcher restart 两条路径;
  • 监控恢复次数、恢复时长、checkpoint 延迟和训练有效吞吐;
  • 把“能够恢复”与“严格复现”分开验收,尤其是数据迭代器和随机数状态。

NVRx 的价值不在于让 GPU 故障消失,而在于把故障从灾难性事件变成可观测、可恢复的运行状态。对于长时间运行的 FSDP 任务,异步 checkpoint 提供持续保护,in-process restart 提供低开销恢复,ft_launcher 则为进程级崩溃提供兜底。三者配合,才构成适合 EKS 生产训练的容错闭环。


相关推荐