
# 多维组调度（Gang）
#### 背景信息
[组调度（Gang）](https://support.huaweicloud.com/usermanual-cce/cce_10_0778.html)满足了调度过程中"All or nothing"的调度需求，避免Pod的任意调度导致集群资源的浪费，主要应用于AI、大数据等多任务协作场景。启用该能力后，可以解决分布式训练任务之间的资源忙等待和死锁等痛点问题，大幅度提升整体训练性能。
随着AI任务的演进，AI任务可以进一步细分为不同维度的子任务，例如分布式训练任务中的多个分片或分布式推理任务中的不同Role。子任务内部及子任务之间出现了新的多维度Pod组调度（Gang）需求。子任务内的多个Pod需在最小资源得到满足后才能启动，以防止子任务内部只有部分Pod启动而导致任务无效运行。当存在多个子任务时，需确保一定数量或一定比例的子任务启动后，任务才能正常运行。为此，Volcano提供了多维组调度（Gang）能力策略，在调度时为不同维度的任务提供组调度（Gang）能力，以满足不同维度任务对资源整体调度的需求。
#### 功能介绍
多维组调度（Gang）支持以下能力：
- 任务维度组调度（Gang）能力 指在调度过程中检查任务下的Pod已调度数量是否达到了最小运行数量。只有当任务内的最小Pod运行数量得到满足时，该任务下的Pod才会被调度到集群中，否则任务调度将不会执行。
  
- 子任务维度组调度（Gang）能力 每个子任务需遵循组调度（Gang）原则。只有当一个子任务内的所有Pod均满足Gang调度条件时，该子任务才会被调度，否则该子任务的调度将不会执行。在集群资源不足的情况下，确保子任务能够整体调度，以保持子任务的完整性，避免因Pod数量不足而导致子任务无效运行，从而浪费资源。
  
- 子任务之间组调度（Gang）能力 在调度过程中，监控任务中不同类型的子任务可调度数量，当最小可调度子任务数量要求得到满足时，该任务下的Pod才会被调度；否则，任务调度将不会执行。当集群资源不足时，允许以子任务为单位进行弹性调度，确保任务内启动的不同类型子任务满足最低部署数量和比例要求，以保障任务的有效运行。
  
Volcano通过PodGroup提供了对任务内部Pod的分组定义，通过配置字段将一个PodGroup内的Pod分成多个SubGroup。在此过程中，可以定义每个SubGroup进行组调度（Gang）的Pod数量以及不同类型SubGroup的最小数量要求。在调度时，每个SubGroup将会遵循各自的组调度条件约束。任务负载可以通过设置关联的PodGroup，利用Volcano对负载进行多维Gang调度。
```
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
  name: pg-test1
  namespace: default
spec:
  subGroupPolicy:                          
  - name: subgroup-test
    subGroupSize: 2                          # 指示subGroup内至少需要几个Pods可被调度
    minSubGroups: 4                          # 指示至少需要多少subGroup可被调度                 
    labelSelector:                           # 分组Pod需要匹配的标签
        matchLabels:
          task-type: test  
    matchLabelKeys:                          # Pod的分组依据，Label value值相同的Pod分到同一个SubGroup中
    - group                                             
  minMember: 8                    #PodGroup内至少需要几个Pods可被调度
```
涉及参数解释如下：
- minMember：指定工作负载最小Pod数量。
- subGroupPolicy： 指定分组策略，将任务中的Pod按照策略分为不同的分组。
  - name：分组策略名字。
  
  - subGroupSize： 指定一个分组里面最小Pod的数量。
  
  - minSubGroups：指定当前分组策略内最小的分组数量。
  
  - labelSelector：被分组的Pod需要匹配的标签。
  
  - matchLabelKeys：Pod的分组依据，根据Pod标签所对应的value值进行分组，value值相同的Pod分到同一个SubGroup中。
   
Volcano Job在Job定义中提供了对应的分组定义和组调度（Gang）约束定义，通过Volcano自动创建和管理PodGroup，并在调度时根据多维Gang调度策略对Volcano Job进行调度。
#### 前提条件
集群中已安装Volcano调度器插件，且插件版本在1.21.1及以上。
#### 配置组调度策略
安装Volcano后，您可通过"配置中心 \> 调度配置"选择开启或关闭Gang调度能力，默认开启。
1. 登录[CCE控制台](https://console.huaweicloud.com/cce2.0/?#/cce/cluster/list)，单击集群名称进入集群。
2. 在左侧选择"配置中心"，在右侧选择"调度配置"页签。
3. 选择Volcano调度器，在"AI任务性能增强调度"配置中，选择是否开启"组调度 (Gang) "。 
   启用该能力后，可增强集群业务的吞吐量，提高业务运行性能。
   
   
4. 修改完成后，单击"确认配置"。 
   配置完成后，可以在工作负载或Volcano Job中使用Gang调度能力。
   
   
 
#### 部署负载使用多维组调度（Gang）能力
本文通过以下场景示例为您介绍如何使用多维组调度（Gang）能力。
#### 创建Volcano Job
通过Volcano Job创建训练任务负载时，可以通过Volcano Job定义不同的训练分片Gang调度策略，实现不同维度的弹性部署。在指定训练任务整体Pods数量的基础上，通过partitionPolicy指定任务内不同分片的分组策略及分片的最少启动数量，确保调度时分片Pod和分片的整体调度。创建Volcano Job时，只需指定minAvailable数量和partitionPolicy即可，Volcano调度器会根据partitionPolicy自动创建并管理PodGroup的分组，示例如下。
```
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
  name: sample
spec:
  schedulerName: volcano
  minAvailable: 5           # 可选，表示Job内最小Pod数量（Gang调度条件）
  tasks:
    - name: "ps"
      replicas: 2           # 必选，表示一个任务内有多少Pod
      partitionPolicy:      # 可选，表示对当前任务内的所有Pod进行分组
        totalPartitions: 2  # 必选，对任务内的一组Pod分为多少组
        partitionSize: 1    # 必选，每组中包含的Pod数量（Gang调度条件），totalPartitions和partitionSize相乘的结果应与replicas相等 
        minPartitions: 1    # 可选，表示分组数量的Gang调度条件
      template:
        spec:
          containers:
            - image: busybox
              command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
              imagePullPolicy: IfNotPresent
              name: ps
              resources:
                requests:
                  cpu: "500m"
          restartPolicy: OnFailure
    - name: "worker"
      replicas: 8           # 必选，表示一个任务内有多少Pod
      partitionPolicy:      # 可选，表示对当前任务内的所有Pod进行分组，按照分组设置网络拓扑亲和的调度能力
        totalPartitions: 4  # 必选，对task内的一组Pod分为多少组       
        partitionSize: 2    # 必选，每组中包含的pod数量，totalPartitions和partitionSize相乘的结果应与replicas相等 
        minPartitions: 2    # 可选，表示分组数量的gang调度条件
      template:
        spec:
          containers:
            - image: busybox
              command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
              imagePullPolicy: IfNotPresent
              name: worker
              resources:
                requests:
                  cpu: "500m"
          restartPolicy: OnFailure
```
示例中组调度（Gang）需要满足以下三个维度：
- partitionSize：Parameter Server（PS）任务中每个分组至少需要1个Pod可调度后该分组可调度，Worker任务中每个分组至少需要2个Pod可调度后该分组可调度。
- minPartitions：每个任务内最小的分组数量，PS任务中至少需要一个分组可调度，Worker任务中至少需要2个分组可调度。
- minAvailable：Job内最小的Pod数量。Job需要至少5个Pod可调度。
当集群资源不足仅满足5个Pod可以被调度时，优先按照多维Gang调度的约束进行Pod调度，确保调度的Pod满足各个Partition的Pod数量要求和Partition的数量要求。PS任务启动了一个完整分组，而Worker任务启动了2个完整分组，从而满足了整体任务的最小启动要求。
执行kubectl get pods命令查看负载信息，返回类似信息如下。
```
NAME              READY   STATUS    RESTARTS   AGE
sample-ps-0       1/1     Running   0          34s
sample-ps-1       0/1     Pending   0          34s
sample-worker-0   1/1     Running   0          34s
sample-worker-1   1/1     Running   0          34s
sample-worker-2   1/1     Running   0          34s
sample-worker-3   1/1     Running   0          34s
sample-worker-4   0/1     Pending   0          61s
sample-worker-5   0/1     Pending   0          61s
sample-worker-6   0/1     Pending   0          61s
sample-worker-7   0/1     Pending   0          61s
```
#### 创建Kthena ModelServing推理负载
当分布式推理负载存在Prefill和Decode两种角色时，每个角色内可以有多个Pod。单个Prefill或Decode角色内的多个Pod需要默认满足Gang调度能力，即一个角色实例中的Pod要么全部被调度，要么一个都不调度。Prefill和Decode角色可以有多个实例，存在不同的组合方式。例如，在4P2D的场景下，只有满足最低2P1D数量要求时，才能为负载Pod分配资源。
Kthena提供的模型服务编排（ModelServing）已支持通过Volcano调度推理负载。在ModelServing中，可以直接定义推理负载在不同维度的Gang调度要求，并通过Volcano实现负载的多维度Gang调度。创建ModelServing时，只需指定gangPolicy和Role，ModelServing控制器将根据gangPolicy和Role定义自动创建并管理PodGroup的分组，示例如下。
```
apiVersion: workload.serving.volcano.sh/v1alpha1
kind: ModelServing
metadata:
  name: sample
  namespace: default
spec:
  schedulerName: volcano
  replicas: 1
  template:
    restartGracePeriodSeconds: 60
    gangPolicy:
      minRoleReplicas:    # 可选，各Role最少Repilca数量的Gang调度条件
        prefill: 2       
        decode: 2
    roles:
      - name: prefill
        replicas: 4        # 必选，Role Replica数量 
        entryTemplate:
          spec:
            containers:
              - name: leader
                image: busybox
                command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
                resources:
                  limits:
                    cpu: "0.5"
                  requests:
                    cpu: "0.5"
        workerReplicas: 1    # 可选，Role Prefill内Worker Pod数量
        workerTemplate:
          spec:
            containers:
              - name: worker
                image: busybox
                command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
                resources:
                  limits:
                    cpu: "0.5"
                  requests:
                    cpu: "0.5"
      - name: decode
        replicas: 4  
        entryTemplate:
          spec:
            containers:
              - name: leader
                image: busybox
                command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
                resources:
                  limits:
                    cpu: "0.5"
                  requests:
                    cpu: "0.5"
        workerReplicas: 1    # 可选，Role Replica内Worker Pod数量
        workerTemplate:
          spec:
            containers:
              - name: worker
                image: busybox
                command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
                resources:
                  limits:
                    cpu: "0.5"
                  requests:
                    cpu: "0.5"
```
示例中组调度（Gang）需要满足是两个维度：
- workerReplicas：每个Role Replica内最少Worker Pod数量。Role Replica内至少需要entryReplica (1) + workerReplicas个Pod可调度后该Role Replica可调度。
- minRoleReplicas：各个Role最小的副本数量，示例中Prefill和Decode各自至少需要2个副本（2P2D）才能调度。
ModelServing工作负载的最少Pod数由最少Role replicas数量\*Role Pod数量计算得到，您无需单独设置。
在资源不足时可以看到Prefill和Decode Role各自启动了2个Replica，每个Replica内2个Pod。
```
NAME                   READY   STATUS    RESTARTS   AGE
sample-0-decode-0-0    1/1     Running   0          8s
sample-0-decode-0-1    1/1     Running   0          8s
sample-0-decode-1-0    1/1     Running   0          8s
sample-0-decode-1-1    1/1     Running   0          7s
sample-0-decode-2-0    0/1     Pending   0          7s
sample-0-decode-2-1    0/1     Pending   0          7s
sample-0-decode-3-0    0/1     Pending   0          7s
sample-0-decode-3-1    0/1     Pending   0          7s
sample-0-prefill-0-0   1/1     Running   0          8s
sample-0-prefill-0-1   1/1     Running   0          8s
sample-0-prefill-1-0   1/1     Running   0          8s
sample-0-prefill-1-1   1/1     Running   0          8s
sample-0-prefill-2-0   0/1     Pending   0          8s
sample-0-prefill-2-1   0/1     Pending   0          8s
sample-0-prefill-3-0   0/1     Pending   0          8s
sample-0-prefill-3-1   0/1     Pending   0          8s
```
