随着深度学习模型参数量的爆炸式增长(从千万级别到千亿级别的 LLM),单机单卡的训练模式早已成为历史。现代机器学习(ML)基础设施的核心诉求是如何高效、稳定、可扩展地在 Kubernetes 集群上运行分布式训练任务。
虽然 Kubernetes 提供了原生的 Job 资源来处理批处理任务,但这对于复杂的分布式机器学习训练(如 TensorFlow 的 Parameter Server 模式,或 PyTorch 的 DDP 模式)来说远远不够。分布式 ML 训练涉及多个角色的协同、复杂的网络拓扑发现、特定的环境变量注入,以及对集群调度器(避免死锁)的特殊要求。
为了解决这些痛点,Kubeflow 核心组件之一 Training Operator 应运而生。本文将带你深入剖析 Training Operator,从核心概念、架构设计,到控制面实现细节、网络注入原理以及 Gang Scheduling(群组调度)等关键技术细节,为你呈现云原生机器学习训练架构的全貌。
要理解 Training Operator,我们需要先理清分布式训练在 K8s 上的几个核心概念。
在 Kubernetes 中,CRD(Custom Resource Definition) 允许用户自定义资源。Operator 是一种特定的设计模式,它包含一个自定义控制器(Controller),该控制器不断监听(Watch)CRD 以及相关资源(Pod, Service)的状态,并在控制循环(Reconcile Loop)中驱动集群状态向用户声明的期望状态(Desired State)逼近。 Training Operator 就是一个极具代表性的 K8s Operator。
Training Operator 提供了一系列 CRD 来抽象不同 ML 框架的分布式训练任务:
ReplicaSpecs 与 角色 (Roles)在每一个 xJob 中,最关键的结构是 ReplicaSpecs。分布式训练由多个实例(Pod)组成,但这些实例的指责并不相同。ReplicaSpecs 定义了不同角色的副本数、镜像和资源请求。
分布式训练通常要求**“全有或全无” (All-or-Nothing)**。例如,一个 4 节点的 PyTorch 任务如果只有 3 个节点被调度,这 3 个节点将会无限期地挂起等待第 4 个节点,同时白白霸占显存。Gang Scheduling 就是为了解决这种死锁而引入的机制。
这几个概念是如何协同运行一名分布式训练任务的?我们可以用以下的关系图来表示:
erDiagram
USER ||--o{ XJOB_CR : "Submits (YAML)"
XJOB_CR ||--|{ REPLICA_SPEC : "Contains"
REPLICA_SPEC {
string Role "eg. Worker, Master, PS"
int Replicas "Number of pods"
object PodTemplate "Container, GPU request"
}
TRAINING_OPERATOR ||--o{ XJOB_CR : "Watches & Reconciles"
TRAINING_OPERATOR ||--|{ POD : "Creates"
TRAINING_OPERATOR ||--|{ SERVICE : "Creates (for discovery)"
POD }o--|| KUBE_SCHEDULER : "Assigned by"
POD }o--|| VOLCANO_SCHEDULER : "Gang Scheduled by"
SERVICE ||--o{ POD : "Provides DNS for"PyTorchJob CR YAML。PyTorchController 监听到这个 CR 的创建。ReplicaSpecs,为 Master 创建 1 个 Pod 和 Service,为 Worker 创建 N 个 Pod 和 Service。在早期的 Kubeflow 架构中,tf-operator、pytorch-operator 是独立的代码库,维护成本极高。现代的 Training Operator 采用了统一的代码架构:Unified Core V1 / Common Controller。
graph TD
API[Kubernetes API Server]
subgraph Training Operator
Manager[Controller Manager]
subgraph Framework Controllers
TFController[TF Controller]
PTController[PyTorch Controller]
MPIController[MPI Controller]
end
subgraph Common Abstract Core
JobController[Common Job Controller Core]
PodManager[Pod / Service Lifecycle Manager]
StatusManager[Status & Condition Updater]
end
Manager --> TFController
Manager --> PTController
Manager --> MPIController
TFController --> JobController
PTController --> JobController
MPIController --> JobController
end
API <-->|Watch/List CRDs <br> Create Pod/Svc| Manager
subgraph Compute Nodes
PodMaster[Master Pod \n+ Environment Variables]
PodWorker1[Worker 1 Pod]
PodWorker2[Worker 2 Pod]
end
Manager -.-> PodMaster
Manager -.-> PodWorker1架构的核心在于 Common Job Controller。大多数 ML 框架在分布式训练的生命周期管理上(创建 Pod -> 创建 Service -> 注入网络发现信息 -> 监控完成状态 -> 清理资源)是非常类似甚至完全一样的。统一架构抽象了这些行为,各个框架专属的 Controller 仅仅需要实现各自特有的逻辑:
作为一万字深度的好文,我们不能仅仅停留在架构图,而是要深入探究 Training Operator 是怎么解决底层工程难题的。
Operator 的核心就是 Control Loop,它的步骤如下:
training.kubeflow.org/job-name)。ReplicaSpecs 的期望数量(Expectations)与当前群集中实际 Running/Pending 的实例数量。PodManager 创建。PodManager 进行 Delete。Failed 或者被驱逐且超出了重启策略上限,则将 Job Phase 置为 Failed;等指定角色(如 Master)变为 Succeeded,Job Phase 置为 Succeeded。这是 Training Operator 最大的价值所在。分布式框架需要知道两件事:1. 我是谁(我的 Rank 是多少)? 2. 我的同伴在哪里(其它节点的 IP 和端口)?
原生的框架跑在物理机时,需要算法工程师手写配置文件或使用命令参数传入。在云原生动态弹性的 Pod 网络中,Pod 的 IP 在启动前是未知的。Training Operator 自动拦截 Pod 创建请求,利用 K8s Service DNS 和环境变量拦截注入技术解决了这个问题。
TF_CONFIG 注入TensorFlow 的分布式训练高度依赖一个名为 TF_CONFIG 的 JSON 格式环境变量。
TF Controller 在创建 Pod 前,会拼装出如下环境变量,并隐式注入到用户的 Pod 中:
{
"cluster": {
"chief": ["tfjob-myjob-chief-0.default.svc:2222"],
"worker": [
"tfjob-myjob-worker-0.default.svc:2222",
"tfjob-myjob-worker-1.default.svc:2222"
],
"ps": ["tfjob-myjob-ps-0.default.svc:2222"]
},
"task": {
"type": "worker",
"index": 1
}
}原理:
Operator 事先确立命名规范 {job_name}-{role}-{index}。它为每一个预期创建的 Pod 创建一个无头服务 (Headless Service) 或使用带端点的普通 Service。在 Pod 拉起时,即使其它节点的 Pod 还没有 Ready,由于 DNS 域名协议 xxx.default.svc.cluster.local 的存在,TF_CONFIG 也已经可以被准确定位。
MASTER_ADDR 和 MASTER_PORTPyTorch 的 DistributedDataParallel (DDP) 主要是基于 TCP 的网络发现,它依赖几个固定的环境变量初始化 ProcessGroup:
MASTER_ADDR="pytorchjob-myjob-master-0"
MASTER_PORT="23456"
WORLD_SIZE="3"
RANK="1" # (当前 Pod 的全局序号)ReplicaSpec[Master] 下的 Replica 0 作为通讯的主节点。Env 数组。WORLD_SIZE 则根据所有角色的 Replicas 总和自动计算出来。用户不再自己填写死板的机器 IP。云原生环境资源高度碎片化。想象一个集群只有 4 卡 GPU,有两个用户同时提交了“需要 4 卡的 PyTorchJob A”和“需要 4 卡的 PyTorchJob B”。 如果默认调度器(kube-scheduler)按 Pod 粒度调度,A 分到 2 卡,B 分到 2 卡。结果是:两者都在等待剩下的 2 卡,从而形成**互相死锁 (Deadlock)**。
Training Operator 为了解决这一问题,深度整合了 Volcano 或 YuniKorn 等批处理调度器。
sequenceDiagram
participant User
participant Operator as Training Operator
participant K8s API
participant Volcano as Volcano Scheduler
User->>Operator: Submit Job (4 GPUs required)
Operator->>K8s API: Create PodGroup (minMember=4)
Operator->>K8s API: Create 4 Pods with annotation "pod-group.scheduling.sigs.k8s.io/...""
K8s API-->>Volcano: Inform new PodGroup & Pods
Note over Volcano: Check total free GPUs
alt Target met
Volcano->>K8s API: Bind all 4 pods simultaneously
else Only 3 GPUs free
Volcano-->>K8s API: Keep pending, Do NOT bind
end实现细节:
Training Operator 会检测集群是否开启了 SchedulingData 特性门控。如果受支持,它会伴随 Job CR 在同命名空间立刻生成一个 PodGroup CRD实例,属性设定为 minMember = SUM(All Replicas)。PodSpec 的 schedulerName 被修改为 volcano,从而在根本上隔离了默认的单次调度引发的资源抢占问题。
早期的分布式训练极为脆弱,任何一台机器(Pod)由于底层硬件故障被 K8s 驱逐,都会导致整个 Job Failed。
Training Operator 对此的抽象是 RestartPolicy (重启策略):
Always / OnFailure: 当某 Worker 失败,Operator 直接拉起一个具有相同 Index 的新 Pod。ExitCode: 仅当进程非 0 退出时重启。但在深度学习框架级别,TF1 等传统框架若 Worker 0 丢失,整个通信子系统会崩溃。因而近年来的核心演进是:**结合 PyTorch Elastic (Torchrun)**。
Operator 已经完全支持 TorchElastic。当开启该属性时,PyTorch 的 Master 被弱化,通信拓扑使用 c10d (依托外部 ETCD/Redis) 提供动态的 Rendezvous:
如果 10 个节点的任务中崩了 2 个,ETCD 会捕捉到节点丢失。剩下的 8 个节点会自动降级,恢复最近一次 Checkpoint并组成只含有 8 节点的新 WORLD_SIZE 继续训练;而被 Operator 重新拉起的 2 个 Pod 回来后,又会再次合并为 10 节点的分布式群组。这是极具里程碑意义的容错能力跃升。
一切架构的最终归宿都是 YAML。我们剖析一份企业级生产配置:
apiVersion: "kubeflow.org/v1"
kind: PyTorchJob
metadata:
name: resnet-distributed
namespace: ml-training
spec:
pytorchReplicaSpecs:
Master:
replicas: 1
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: pytorch/pytorch:2.0.0-cuda11.7-cudnn8-devel
command:
- "python"
- "/var/train.py"
resources:
requests:
nvidia.com/gpu: 1
cpu: 4
memory: 16Gi
Worker:
replicas: 3
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: pytorch/pytorch:2.0.0-cuda11.7-cudnn8-devel
command:
- "python"
- "/var/train.py"
resources:
requests:
nvidia.com/gpu: 1
cpu: 4
memory: 16Gi当该配置提交后,Training Operator 将构建出一个 4卡(1 Master + 3 Worker)的多节点分布式拓扑,并在后台自动化解为 K8s Pods,注入 MASTER_ADDR=resnet-distributed-master-0 以及对应 Rank 从而启动运算。
Kubeflow Training Operator 通过高度抽象的通用作业控制器架构,不仅极具优雅地涵盖了 TensorFlow, PyTorch, XGBoost 等所有主流 AI 框架,更是云原生 AI 领域真正的“多卡/多机大总管”。
它的核心技术价值在于三点:
随着大语言模型(LLM)与集群规模的继续扩大(如万卡 GPU 集群时代的到来),Training Operator 面临的下一个挑战是如何更好地支持 Megatron-LM 等复杂的 3D 并行(数据并行 DP / 张量并行 TP / 流水线并行 PP)的精细化亲和性调度。未来的 Training Operator 必将朝着**拓扑感知 (Topology-Aware Scheduling)**的深水区航行,为 AI 提供最坚实的基础。