Job

Job 调度

必须一起调度的一批 Pod 称为 Job。

PodGroup

有时,这批 Pod 是完全同质的,只需要累积到指定的最小数量即可成功调度。在这种情况下,我们可以通过单独的 PodGroup 描述 minMember,然后通过 Pod Label 关联其 member Pod。以下是一个最小累积数量为 2 的 PodGroup 及其 member Pod。

1. apiVersion: scheduling.sigs.k8s.io/v1alpha1
2. kind: PodGroup
3. metadata:
4. name: gang-example
5. namespace: default
6. spec:
7. minMember: 2
1. apiVersion: v1
2. kind: pod
3. metadata:
4. name: pod-example1
5. namespace: default
6. labels:
7. pod-group.scheduling.sigs.k8s.io: gang-example
8. spec:
9. schedulerName: koord-scheduler
10. ...
11. ---
12. apiVersion: v1
13. kind: pod
14. metadata:
15. name: pod-example2
16. namespace: default
17. labels:
18. pod-group.scheduling.sigs.k8s.io: gang-example
19. spec:
20. schedulerName: koord-scheduler
21. ...

GangGroup

在其他情况下,必须一起调度的 Pod 可能不是同质的,必须分别完成最小数量的累积。在这种情况下,Koordinator 支持通过 PodGroup Label 关联不同的 PodGroup 以形成 GangGroup。以下是一个包含两个 PodGroup 的 GangGroup:

1. apiVersion: scheduling.sigs.k8s.io/v1alpha1
2. kind: PodGroup
3. metadata:
4. name: gang-example1
5. namespace: default
6. annotations:
7. gang.scheduling.koordinator.sh/groups: "[\"default/gang-example1\", \"default/gang-example2\"]"
8. spec:
9. minMember: 1
10. ---
11. apiVersion: scheduling.sigs.k8s.io/v1alpha1
12. kind: PodGroup
13. metadata:
14. name: gang-example2
15. namespace: default
16. annotations:
17. gang.scheduling.koordinator.sh/groups: "[\"default/gang-example1\", \"default/gang-example2\"]"
18. spec:
19. minMember: 2

Job 级别抢占

当 Pod 由于资源不足而无法调度时,Kube-Scheduler 会尝试驱逐低优先级 Pod 以为其腾出空间。这是传统的 Pod 级别抢占。然而,当 Job 由于资源不足而无法调度时,调度器必须为整个 Job 的调度腾出足够的空间。这种类型的抢占称为 Job 级别抢占。

抢占算法

发起抢占的作业称为 抢占者(preemptor),被抢占的 Pod 称为 受害者(victim)。Job 级别抢占的整体工作流程如下:

  1. 不可调度的 Pod → 进入 PostFilter 阶段
  2. 是否是 Job? → 是 → 获取所有成员 Pod
  3. 检查 Job 抢占资格:
  4. pods.spec.preemptionPolicy ≠ Never
  5. 所有成员 Pod 当前提名的节点上没有正在终止的受害者(防止冗余抢占)
  6. 查找抢占可能有帮助的候选节点
  7. 执行预演以模拟删除潜在受害者(低优先级 Pod)
  8. 选择最优节点 + 最小成本受害者集(作业感知成本模型)
  9. 执行抢占:
  10. 删除受害者(通过设置 DisruptionTarget 条件并调用删除 API)
  11. 清除目标节点上其他低优先级提名 Pod 的 status.nominatedNode。
  12. 为所有成员 Pod 设置 status.nominatedNode。
  13. 抢占成功 → Pod 进入调度队列,等待受害者终止。

受害者的抢占原因

当受害者被抢占时,Koord-Scheduler 会在 victim.status.conditions 中添加一个条目,以指示哪个作业抢占了它并触发优雅终止。

1. apiVersion: v1
2. kind: pod
3. metadata:
4. name: victim-1
5. namespace: default
6. status:
7. conditions:
8. - lastProbeTime: null
9. lastTransitionTime: "2025-09-17T08:41:35Z"
10. message: 'koord-scheduler: preempting to accommodate higher priority pods, preemptor:
11. default/hello-job, triggerpod: default/preemptor-pod-2'
12. reason: PreemptionByScheduler
13. status: "True"
14. type: DisruptionTarget

上面显示 default/victim-1 被高优先级作业 hello-job 抢占。可以通过以下命令检索 hello-job 的成员 Pod:

1. $ kubectl get po -n default -l pod-group.scheduling.sigs.k8s.io=hello-job
2. hello-job-pod-1   0/1     Pending             0                5m
3. hello-job-pod-2   0/1     Pending             0                5m

抢占者的提名节点

Job 抢占成功后,除了驱逐受害 Pod 外,调度器还必须在其内部缓存中预留回收的资源。在 Kubernetes 中,这是使用 pod.status.nominatedNode 实现的。在 Koordinator 中,koord-scheduler 为抢占作业的所有成员 Pod 设置 .status.nominatedNode 字段以反映此资源预留。

1. apiVersion: v1
2. kind: pod
3. metadata:
4. name: preemptor-pod-1
5. namespace: default
6. labels:
7. pod-group.scheduling.sigs.k8s.io: hello-job
8. status:
9. nominatednodeName: example-node
10. phase: Pending
11. ---
12. apiVersion: v1
13. kind: pod
14. metadata:
15. name: preemptor-pod-2
16. namespace: default
17. labels:
18. pod-group.scheduling.sigs.k8s.io: hello-job
19. status:
20. nominatednodeName: example-node
21. phase: Pending

上面显示 hello-job 的两个 Pod 已成功完成抢占,并被提名调度到 example-node。

网络拓扑感知

在大规模 AI 训练场景中,尤其是大语言模型(LLM)训练,高效的 Pod 间通信对训练性能至关重要。模型并行技术,如张量并行(TP)、流水线并行(PP)和数据并行(DP),需要在 GPU 之间进行频繁的高带宽数据交换——通常跨越多个节点。在这些工作负载下,网络拓扑成为关键的性能瓶颈,通信延迟和带宽受物理网络层次结构(如 NVLink、block、spine)的严重影响。

image

为了优化训练效率,GangGroup 中的 Pod 被要求或优先调度到位于相同或附近高性能网络域的节点上,最小化节点间跳数并最大化吞吐量。例如,在 spine-block 架构中,将所有成员 Pod 调度到同一 block 或 spine 交换机下,与将它们分散在不同 spine 相比,可以显著降低通信延迟。

拓扑感知调度需求

虽然 Kubernetes 的原生调度器通过 PodAffinity 支持基本的拓扑约束,但它以每个 Pod 为基础运行,缺乏 Gang 调度语义,使其对于紧密耦合工作负载的协调放置无效。Koord-Scheduler 抽象了 PodGroup 和 GangGroup 概念,提供全有或全无的语义,实现相互依赖 Pod 的集体调度。此外,为了满足现代 AI 训练的需求,我们将其扩展为网络拓扑感知调度——一种基于网络层次结构智能选择最优节点的能力。

此功能确保:

  • 当集群资源充足时,具有网络拓扑调度需求的 Pod 将根据用户指定的策略被调度到性能更好的拓扑域(例如,更低的延迟、更高的带宽)。
  • 当集群资源不足时,调度器将基于网络拓扑约束通过 Job 级别抢占为 GangGroup 抢占资源,并在 .status.nominatedNode 字段中记录资源提名,以确保一致的放置。

集群网络拓扑

使用 NVIDIA 的 topograph 等工具为节点标记其网络拓扑位置:

1. apiVersion: v1
2. kind: Node
3. metadata:
4. name: node-0
5. labels:
6. network.topology.nvidia.com/accelerator: nvl1
7. network.topology.nvidia.com/block: s1
8. network.topology.nvidia.com/spine: s2
9. network.topology.nvidia.com/datacenter: s3

管理员通过名为 default 的 ClusterNetworkTopology CR 定义拓扑层次结构:

1. apiVersion: scheduling.koordinator.sh/v1alpha1
2. kind: ClusterNetworkTopology
3. metadata:
4. name: default
5. spec:
6. networkTopologySpec:
7. - labelKey:
8. - network.topology.nvidia.com/spine
9. topologyLayer: SpineLayer
10. - labelKey:
11. - network.topology.nvidia.com/block
12. parentTopologyLayer: SpineLayer
13. topologyLayer: BlockLayer
14. - parentTopologyLayer: BlockLayer
15. topologyLayer: NodeTopologyLayer

拓扑形成树状结构,其中每一层代表网络中的聚合级别(例如,Node → block → spine)。

ClusterNetworkTopology 的 status.detailStatus 字段由 Koordinator 自动维护,反映集群中的实际网络拓扑结构和节点分布。它呈现从顶层(集群)到各个节点的分层视图。detailStatus 中的每个条目代表特定拓扑层的一个实例,具有关键字段:

  • topologyInfo: 当前层的类型和名称(例如,SpineLayer,s1)。
  • parentTopologyInfo: 父层的信息。
  • childTopologyNames: 下一较低层中的子域列表。
  • nodeNum: 此拓扑域内的节点数量。

以下是 clusterNetworkTopology.status.detailStatus 的示例:

1. apiVersion: scheduling.koordinator.sh/v1alpha1
2. kind: ClusterNetworkTopology
3. metadata:
4. name: default
5. spec:
6. networkTopologySpec:
7. - labelKey:
8. - network.topology.nvidia.com/spine
9. topologyLayer: SpineLayer
10. - labelKey:
11. - network.topology.nvidia.com/block
12. parentTopologyLayer: SpineLayer
13. topologyLayer: BlockLayer
14. - parentTopologyLayer: BlockLayer
15. topologyLayer: NodeTopologyLayer
16. status:
17. detailStatus:
18. - childTopologyLayer: SpineLayer
19. childTopologyNames:
20. - s1
21. - s2
22. nodeNum: 8
23. topologyInfo:
24. topologyLayer: ClusterTopologyLayer
25. topologyName: ""
26. - childTopologyLayer: BlockLayer
27. childTopologyNames:
28. - b2
29. - b1
30. nodeNum: 4
31. parentTopologyInfo:
32. topologyLayer: ClusterTopologyLayer
33. topologyName: ""
34. topologyInfo:
35. topologyLayer: SpineLayer
36. topologyName: s1
37. - childTopologyLayer: NodeTopologyLayer
38. nodeNum: 2
39. parentTopologyInfo:
40. topologyLayer: SpineLayer
41. topologyName: s1
42. topologyInfo:
43. topologyLayer: BlockLayer
44. topologyName: b2
45. - childTopologyLayer: NodeTopologyLayer
46. nodeNum: 2
47. parentTopologyInfo:
48. topologyLayer: SpineLayer
49. topologyName: s1
50. topologyInfo:
51. topologyLayer: BlockLayer
52. topologyName: b1
53. - childTopologyLayer: BlockLayer
54. childTopologyNames:
55. - b3
56. - b4
57. nodeNum: 4
58. parentTopologyInfo:
59. topologyLayer: ClusterTopologyLayer
60. topologyName: ""
61. topologyInfo:
62. topologyLayer: SpineLayer
63. topologyName: s2
64. - childTopologyLayer: NodeTopologyLayer
65. nodeNum: 2
66. parentTopologyInfo:
67. topologyLayer: SpineLayer
68. topologyName: s2
69. topologyInfo:
70. topologyLayer: BlockLayer
71. topologyName: b3
72. - childTopologyLayer: NodeTopologyLayer
73. nodeNum: 2
74. parentTopologyInfo:
75. topologyLayer: SpineLayer
76. topologyName: s2
77. topologyInfo:
78. topologyLayer: BlockLayer
79. topologyName: b4

基于上述 status,集群具有两层 spine-block 架构:

1. ClusterTopologyLayer
2. ├── SpineLayer: s1
3. │   ├── BlockLayer: b1
4. │   │   └── NodeTopologyLayer: 2 nodes
5. │   └── BlockLayer: b2
6. │       └── NodeTopologyLayer: 2 nodes
7. └── SpineLayer: s2
8. ├── BlockLayer: b3
9. │   └── NodeTopologyLayer: 2 nodes
10. └── BlockLayer: b4
11. └── NodeTopologyLayer: 2 nodes

网络拓扑策略

当用户想要为 GangGroup 配置网络拓扑聚集策略时,可以按如下方式注解其 PodGroup:

1. apiVersion: scheduling.sigs.k8s.io/v1alpha1
2. kind: PodGroup
3. metadata:
4. name: gang-master
5. namespace: default
6. annotations:
7. gang.scheduling.koordinator.sh/groups: ["default/gang-master", "default/gang-worker"]
8. gang.scheduling.koordinator.sh/network-topology-spec: |
9. {
10. "gatherStrategy": [
11. {
12. "layer": "SpineLayer",
13. "strategy": "PreferGather"
14. },
15. {
16. "layer": "BlockLayer",
17. "strategy": "PreferGather"
18. },
19. {
20. "layer": "AcceleratorLayer",
21. "strategy": "PreferGather"
22. }
23. ]
24. }
25. spec:
26. minMember: 1
27. ---
28. apiVersion: scheduling.sigs.k8s.io/v1alpha1
29. kind: PodGroup
30. metadata:
31. name: gang-worker
32. namespace: default
33. annotations:
34. gang.scheduling.koordinator.sh/groups: ["default/gang-master", "default/gang-worker"]
35. gang.scheduling.koordinator.sh/network-topology-spec: |
36. {
37. "gatherStrategy": [
38. {
39. "layer": "SpineLayer",
40. "strategy": "PreferGather"
41. },
42. {
43. "layer": "BlockLayer",
44. "strategy": "PreferGather"
45. },
46. {
47. "layer": "AcceleratorLayer",
48. "strategy": "PreferGather"
49. }
50. ]
51. }
52. spec:
53. minMember: 2

上述 PodGroup 表示属于它的 Pod 首先尝试位于加速器互连域中,然后尝试位于 Block 中,最后尝试位于 Spine 网络中。

PodCountMultiple

在一些分布式训练场景中,放置在每个拓扑域内的 Pod 数量必须是特定值 X 的整数倍,其中 X 与域内的通信参数(例如张量并行度)相关。例如,如果 TP=8,则每个 Block 应精确包含 0 或 8 个 Pod——不允许部分组。可以通过聚集策略中的 podCountMultiple 字段进行配置:

1. apiVersion: scheduling.sigs.k8s.io/v1alpha1
2. kind: PodGroup
3. metadata:
4. name: gang-example
5. namespace: default
6. annotations:
7. gang.scheduling.koordinator.sh/network-topology-spec: |
8. {
9. "gatherStrategy": [
10. {
11. "layer": "SpineLayer",
12. "strategy": "PreferGather"
13. },
14. {
15. "layer": "BlockLayer",
16. "strategy": "PreferGather",
17. "podCountMultiple": 8
18. }
19. ]
20. }
21. spec:
22. minMember: 16

在上面的示例中,BlockLayer 的 podCountMultiple: 8 表示分配到每个 Block 的 Pod 数量必须是 8 的整数倍。在计算 offerslots 时,算法会将每个拓扑节点的 offerslots 向下取整为该层指定的 podCountMultiple 值的最近整数倍。

有时,由于对通信带宽的严格需求,用户可能希望将 GangGroup 的所有成员 Pod 放置在同一 Spine 下。在这种情况下,您可以按如下方式修改 PodGroup:

1. apiVersion: scheduling.sigs.k8s.io/v1alpha1
2. kind: PodGroup
3. metadata:
4. name: gang-master
5. namespace: default
6. annotations:
7. gang.scheduling.koordinator.sh/groups: ["default/gang-master", "default/gang-worker"]
8. gang.scheduling.koordinator.sh/network-topology-spec: |
9. {
10. "gatherStrategy": [
11. {
12. "layer": "spineLayer",
13. "strategy": "MustGather"
14. }
15. ]
16. }
17. spec:
18. minMember: 1
19. ---
20. apiVersion: scheduling.sigs.k8s.io/v1alpha1
21. kind: PodGroup
22. metadata:
23. name: gang-worker
24. namespace: default
25. annotations:
26. gang.scheduling.koordinator.sh/groups: ["default/gang-master", "default/gang-worker"]
27. gang.scheduling.koordinator.sh/network-topology-spec: |
28. {
29. "gatherStrategy": [
30. {
31. "layer": "spineLayer",
32. "strategy": "MustGather"
33. }
34. ]
35. }
36. spec:
37. minMember: 2

网络拓扑 Pod 索引

在分布式训练中,为每个 Pod 分配索引对于在数据并行(DP)作业中建立通信模式至关重要。索引确定 Pod 在集体操作中的逻辑顺序。例如,对于 DP=2 的 GangGroup,成员 Pod 可以注解如下:

1. apiVersion: v1
2. kind: Pod
3. metadata:
4. name: pod-example1
5. namespace: default
6. labels:
7. pod-group.scheduling.sigs.k8s.io: gang-example
8. annotations:
9. gang.scheduling.koordinator.sh/network-topology-index: "1"
10. spec:
11. schedulerName: koord-scheduler
12. ...
13. ---
14. apiVersion: v1
15. kind: Pod
16. metadata:
17. name: pod-example2
18. namespace: default
19. labels:
20. pod-group.scheduling.sigs.k8s.io: gang-example
21. annotations:
22. gang.scheduling.koordinator.sh/network-topology-index: "2"
23. spec:
24. schedulerName: koord-scheduler
25. ...

拓扑聚集算法

网络拓扑聚集算法是为 M 个 Pod 找到最佳节点,给定属于并行感知 GangGroup 的 M 个成员 Pod、所有可以放置 Pod 的节点、每个节点的网络拓扑位置。整体计算过程可以逐步描述如下:

  1. 训练任务的 GangGroup 的成员 Pod 通常是同质的。我们从成员 Pod 中随机选择一个作为代表 Pod。
  2. 从网络拓扑层次结构的底部到顶部,递归计算每个拓扑节点可以提供的成员 Pod 数量作为 offerslots。节点可以容纳的 offerslots 可以通过迭代调用 NodeInfo.AddPod、fwk.RunPreFilterExtensionsAddPod 和 fwk.RunFilterWithNominatedNode 来实现。
  3. 在所有可以容纳 GangGroup 所有成员 Pod 的拓扑节点中,选择那些层级最低的作为我们的 候选拓扑节点。

topology_offerslot_candidatenode 4. 在步骤 3 中选择的候选拓扑节点中,根据 binpack 原则,选择 offerslot 最接近作业所需 offerslot 的候选拓扑节点作为我们的最终拓扑节点解决方案。如下图所示,我们选择 Node5-Node8 作为作业的最终调度结果。

topology_final_placement

下一步是什么: