MPIJob

OpenMPI 是一个开源的 MPI(Message Passing Interface)协议的实现项目。

MPIJob 是一种使用 OpenMPI 进行分布式计算的 T9k Job,此资源让你能够方便地在集群环境中使用 OpenMPI 进行训练。

创建 MPIJob

下面的 MPIJob 示例创建了 5 个执行副本,每个执行副本启动 3 个进程,运行随机游走程序:

apiVersion: batch.tensorstack.dev/v1beta1
kind: MPIJob
metadata:
  name: mpi-example
spec:
  mpiConfig:
    home: /usr/local
    mca:
      btl: ^openib
    extraArgs:
      - -N
      - "3"
      - --enable-recovery
      - --max-restarts
      - "100"
      - --allow-run-as-root
      - -bind-to
      - none
    script:
      - ./random_walk
      - "20"
      - "40"
      - "2"
  ssh:
    sshdPath: /usr/sbin/sshd
  runPolicy:
    cleanUpWorkers: true
  replicaSpecs:
  - type: worker
    replicas: 4
    template:
      spec:
        containers:
          - name: worker
            image: t9kpublic/mpi-tutorial:2021022-2
            resources:
              limits:
                cpu: 100m
              requests:
                cpu: 50m
            workingDir: /usr/local/code
  - type: launcher
    replicas: 1
    template: {}

在该例中:

  • 创建 5 个执行副本(由 spec.replicaSpecs 数组中 typeworker 的部分指定)。
  • spec.replicaSpecs[*].template 字段沿用 PodTemplate 的规约,配置执行副本和启动副本的环境。
  • 在执行副本准备完毕后,启动副本向执行副本发送启动命令,令执行副本创建 3 个 MPI 进程,这些进程分别执行 ./random_walk 20 40 2(由 spec.mpiConfig.script 字段指定)命令。
  • 在训练过程中不使用 Infiniband 进行通信(由 spec.mpiconfig.mca.btl 字段指定)。
  • 在训练结束后自动清除副本(由 spec.runPolicy.cleanUpWorkers 字段指定)来释放集群资源。
  • sshd 的路径为 /user/sbin/sshd(由 spec.ssh.sshdPath 字段指定,使用该字段的原因是 sshd 程序必须使用绝对路径调用,所以需要其具体路径)。
  • MPI 安装在 /usr/local 处(由 spec.mpiConfig.home 字段指定,使用该字段的原因是 mpirun 的有些功能需要知道 MPI 的根目录地址才能正确运行)。

运行 Horovod 训练脚本

使用 Horovod 框架的分布式训练脚本也可以使用 MPIJob 进行训练。

在 MPIJob 中需要执行以下操作:

  1. spec.replicaSpecs[worker].template.spec.containers[worker].env 字段中添加 NCCL_DEBUG
  2. spec.mca 字段中添加 pml:ob1btl:^openib

下面是使用 MPIJob 执行 Horovod 框架的分布式训练脚本的示例:

apiVersion: batch.tensorstack.dev/v1beta1
kind: MPIJob
metadata:
  name: mpi-example
spec:
  mca:
    btl: ^openib
    pml: ob1
  replicaSpecs:
  - type: worker
    template:
      spec:
        containers:
          - name: worker
            env: 
            - name: "NCCL_DEBUG"
              value: "INFO"
...

副本设置

MPIJob 副本运行环境和命令可以通过 spec.replicaSpecs[worker].template 进行配置,可配置内容包括镜像、运行命令、资源配置、环境变量等。

资源配置

副本资源配置通过 spec.replicaSpecs[worker].template.spec.containers[*].resources 字段指定。

MPIJob 的资源配置包括两部分:

  • 资源请求量(requests):创建该副本时,节点上至少应具有这些数量的资源。如果集群中所有节点都不满足副本的资源请求量,则副本的创建可能会被阻塞;或者如果副本的优先级较高,则有可能驱逐节点上其他工作负载来为副本空出可用的资源。
  • 资源上限(limits):该副本在运行期间,最多可以使用的资源数量。比如:如果副本在运行时申请分配超过上限的内存,则有可能出现 OOMKILLED 错误。(注:资源上限不能小于资源请求量)

在下面的示例中,MPIJob 中每个副本设置了以下资源配置:

  • 资源请求量:2 个 cpu 核心、2Gi 内存;
  • 资源上限:4 个 cpu 核心、4Gi 内存。
apiVersion: batch.tensorstack.dev/v1beta1
kind: MPIJob
metadata:
  name: mpi-example
spec:
  replicaSpecs:
  - type: worker
    replicas: 4
    template:
      spec:
        containers:
        - resources:
            limits:
              cpu: 4
              memory: 4Gi
            requests:
              cpu: 2
              memory: 2Gi

共享内存

在进行多节点任务时,可以按照如下方式修改 MPIJob 来使用共享内存:

apiVersion: batch.tensorstack.dev/v1beta1
kind: MPIJob
metadata:
  name: mpi-example
spec:
  replicaSpecs:
  - type: worker
    replicas: 4
    template:
      spec:
        containers:
        - ...
          volumeMounts:
            - mountPath: /dev/shm
              name: dshm
        volumes:
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: "1Gi"

在该例中:

  • spec.replicaSpecs[worker].template.spec.volumes 中增加一项,名称为 dshm,其中限制共享内存最大为 1Gi
  • spec.replicaSpecs[worker].template.spec.containers[*].volumeMounts 中增加一项,将上述 dshm 绑定到 /dev/shm 路径。

环境变量

副本环境变量通过 spec.replicaSpecs[worker].template.spec.containers[*].env 字段指定。MPIJob 支持直接设置环境变量内容和引用其他资源字段作为环境变量两种方式。

在下面的示例中,MPIJob 给副本设置了两个环境变量:ENV_DIRECTENV_REFERENCED。其中 ENV_DIRECT 环境变量被直接设置为 env-valueENV_REFERENCED 环境变量引用了 secret-name Secret 的 key-in-secret 字段的内容。

apiVersion: batch.tensorstack.dev/v1beta1
kind: MPIJob
metadata:
  name: mpi-example
spec:
  replicaSpecs:
  - type: worker
    replicas: 4
    template:
      spec:
        containers:
          - env:
            - name: ENV_DIRECT
              value: env-value
            - name: ENV_REFERENCED
              valueFrom:
                secretKeyRef:
                  name: secret-name
                  key: key-in-secret

调试模式

MPIJob 支持调试模式。在该模式下,训练环境会被部署好,但不会启动训练,用户可以连入副本测试环境或脚本。

该模式可以通过 spec.runMode.debug 字段来设置:

  • spec.runMode.debug.enabled 表示是否启用调试模式。
  • spec.runMode.debug.replicaSpecs 表示如何配置各个副本的调试模式:
    • spec.runMode.debug.replicaSpecs.type 表示作用于的副本类型。
    • spec.runMode.debug.replicaSpecs.skipInitContainer 表示让副本的 InitContainer 失效,默认为 false
    • spec.runMode.debug.replicaSpecs.command 表示副本在等待调试的时候执行的命令,launcher 的默认命令为 sleep infworker 的默认命令为 /usr/bin/sshd -D
    • 如果不填写 spec.runMode.debug.replicaSpecs 字段,则表示所有副本都使用默认设置。

在下面的示例中:

  • 示例一:开启了调试模式,并配置 worker 跳过 InitContainer,并执行 sleep inf
  • 示例二:开启了调试模式,副本使用默认调试设置,即 worker 不跳过 InitContainer,并执行 /usr/bin/sshd -D
# 示例一
...
spec:
  runMode:
    debug:
      enabled: true
      replicaSpecs:
        - type: worker
          skipInitContainer: true
          command: ["sleep", "inf"]

---
# 示例二
...
spec:
  runMode:
    debug:
      enabled: true

暂停模式

MPIJob 支持暂停模式。在该模式下,删除(或不创建)副本,停止训练。

该模式可以通过 spec.runMode.pause 字段来设置:

  • spec.runMode.pause.enabled 表示是否启用暂停模式。
  • spec.runMode.pause.resumeSpecs 表示结束暂停后,如何恢复各个副本:
    • spec.runMode.pause.resumeSpecs.type 表示作用于的副本类型。
    • spec.runMode.pause.resumeSpecs.skipInitContainer 表示让副本的 InitContainer 失效,默认为 false
    • spec.runMode.pause.resumeSpecs.commandspec.runMode.pause.resumeSpecs.args 表示副本在恢复运行时候执行的命令,默认使用 spec.replicaSpecs[*].template 中的命令。
    • 如果不填写 spec.runMode.pause.resumeSpecs 字段,则表示所有副本都使用默认设置。

用户可以随时修改 spec.runMode.pause.enabled 来控制任务暂停,但是不可以更改 spec.runMode.pause.resumeSpecs,所以如果有暂停 MPIJob 的需求,请提前设置好恢复设置。

在下面的示例中:

  • 示例一:开启了暂停模式,并配置 worker 跳过 InitContainer,并执行 /usr/bin/sshd
  • 示例二:开启了暂停模式,副本使用默认恢复设置,即不跳过 InitContainer,并执行 spec.replicaSpecs[*].template 中设置的命令。
# 示例一
...
spec:
  runMode:
    pause:
      enabled: true
      resumeSpecs:
        - type: worker
          skipInitContainer: true
          command: ["/usr/bin/sshd"]

---
# 示例二
...
spec:
  runMode:
    pause:
      enabled: true

MPIJob 状态

MPIJob 的状态和阶段

status.conditions 字段用于描述当前 MPIJob 的状态,包括以下 5 种类型:

  1. Initialized:MPIJob 已经成功创建各子资源,完成初始化。
  2. Running:开始执行任务。
  3. ReplicaFailure:有一个或多个副本出现错误。
  4. Completed:MPIJob 结束
  5. Failed:MPIJob 失败。
  6. Paused:MPIJob 进入暂停模式,所有副本都已删除或正在删除。

status.phase 字段用于描述当前 MPIJob 所处的阶段,MPIJob 的整个生命周期主要有以下几个阶段:

  1. Pending:MPIJob 刚刚创建,等待副本启动。
  2. Running:副本创建成功,开始执行任务。
  3. Paused:MPIJob 进入暂停模式。
  4. Resuming:MPIJob 正从暂停模式中恢复运行。恢复运行后,切换为 Running 阶段。
  5. Succeeded:MPIJob 结束
  6. Failed:MPIJob 失败。
  7. Unknown:控制器无法获得 MPIJob 的阶段。

在下面的示例中,MPIJob 所有子资源创建成功,所以类型为 Initalizedcondition 被设为 True;MPIJob 运行结束,所以类型为 Completedcondition 被设置为 True

...
status:
  conditions:
  - lastTransitionTime: "2024-09-10T02:01:36Z"
    message: The job has been initialized successfully.
    reason: '-'
    status: "True"
    type: Initialized
  - lastTransitionTime: "2024-09-10T02:01:51Z"
    message: The job has finished successfully.
    reason: Succeeded
    status: "False"
    type: Running
  - lastTransitionTime: "2024-09-10T02:01:51Z"
    message: The job has finished successfully.
    reason: Succeeded
    status: "False"
    type: Failed
  - lastTransitionTime: "2024-09-10T02:01:51Z"
    message: The job has finished successfully.
    reason: Succeeded
    status: "True"
    type: Completed
  - lastTransitionTime: "2024-09-10T02:01:51Z"
    message: The job has finished successfully
    reason: Finished
    status: "False"
    type: ReplicaFailure
  phase: Succeeded

副本的状态

status.tasks 字段用来记录副本的状态,记录的内容主要包括:

  • 副本的重启次数(同一种角色的副本的重启次数之和)
  • 副本当前的运行阶段
  • 副本在集群中对应的 Pod 的索引信息

在下面的示例中,MPIJob 创建了 1 个启动副本和 2 个执行副本。当前均处于 Running 阶段,分别运行在 mpi-example-worker-0mpi-example-worker-1 这 2 个 Pod 上;启动副本当前处于 Running 阶段,运行在 mpi-example-launcher Pod 上。

...
status:
  tasks:
  - type: launcher
    restartCount: 0
    status:
    - phase: Running
      name: mpi-example-launcher
      uid: 66634db2-35e7-4641-a4dc-adbd5479734e
      containers: []
  - type: worker
    restartCount: 0
    status:
    - phase: Running
      name: mpi-example-worker-0
      uid: e3ec2ee3-6645-4e21-993f-1e472b94e0ae
      containers: []
    - phase: Running
      name: mpi-example-worker-1
      uid: 908a93f0-7b8b-491e-85d5-3da0abcb4ca4
      containers: []

副本状态统计

status.aggregate 字段统计了各个阶段的副本数量。

在下面示例中,MPIJob 创建了 3 个副本,其中 1 个处于 Pending 阶段,另外两个处于 Running 阶段。

...
status:
  aggregate:
    creating: 0
    deleted: 0
    failed: 0
    pending: 1
    running: 2
    succeeded: 0
    unknown: 0
...