Kubeflow Trainer v2.4: Bringing Workload-Aware Scheduling to TrainJob
- Why is WAS Important for TrainJob?
- Hierarchical Scheduling
- Level 1 - TrainJob Level (Zone)
- 1. ClusterTrainingRuntime and TrainJob Definitions
- 2. Workload Resource Created by TrainJob Controller
- 3. Automatically Generated PodGroup by the TrainJob controller
- 4. Resulting Pod Spec Association
- Level 2 - ReplicatedJob Level (Network Block)
- Level 3 - Job / Per-Replica Level (Rack-Level)
- Kubeflow Trainer v2.4 creates PodGroups at the level you choose
- Introducing the Three Levels in TrainJob Topology
- Adopting CompositePodGroup - ROADMAP
- Other Feature Highlights in Trainer v2.4
- Breaking Changes & Upgrade Notices (v2.4)
- References
- Join the Community
Imagine launching an AI/ML distributed training job across 64 GPUs. Two frequent problems happen:
- Idle Resource Trap: The cluster finds capacity for 63 GPUs and sits idle waiting for the 64th to join, wasting massive resources and increasing the cost. Progress is zero.
- Dependency Deadlock: Suppose your training pipeline requires one initializer (to download datasets) and 64 GPU workers (to train). Kubernetes allocates all 64 GPUs to the workers within your quota, so the initializer cannot be scheduled. The workers cannot start without the data, and the initializer cannot be scheduled without capacity: a deadlock.
Standard Kubernetes struggles with AI/ML distributed training (data/model parallelism) that needs the jobs/tasks to be done via groups/gangs of pods.
To solve these problems, the Kubernetes community introduced the Workload API, which brings gang scheduling, topology-aware scheduling, workload-aware preemption, DRA integration, and more through the Workload, PodGroup, and CompositePodGroup objects.
In this blog, we walk you through why TrainJob adopts Workload-Aware Scheduling (WAS), why it matters for AI/ML distributed training, and the architectural tradeoffs and future work involved. TrainJob implements WAS at three levels, and we explain each level and the reasoning behind it.
You’ll also learn how it simplifies the workflow for both data scientists and platform engineers to avoid boilerplate code.
Why is WAS Important for TrainJob?
By default, kube-scheduler uses a greedy approach, trying to schedule pods one at a time instead of planning for the whole group. It has no native understanding of PyTorch, TrainJobs, or the needs of distributed ML training, where all pods must be scheduled together and may have specific placement requirements, such as running on the same rack.
To bridge this gap, Kubernetes SIG-Scheduling started a Workload-Aware Scheduling (WAS) working group to establish WAS building blocks: Workload, PodGroup, and CompositePodGroups. However, these building blocks require actual workload owners like the TrainJob controller to create such resources.
The TrainJob controller creates Workload objects to guide kube-scheduler on how to admit the pods as a unified group, enforcing all-or-nothing gang scheduling so expensive GPUs are never wasted, alongside topology-aware scheduling for low communication latency.
Best of all, Trainer APIs hide these complex, low-level scheduling resources from data scientists: practitioners simply specify numNodes in their TrainJob while platform administrators pre-configure ClusterTrainingRuntime with the desired scheduling configuration.
Hierarchical Scheduling
To understand the three levels, we must look at how Kubernetes scheduling aligns with the physical reality of an AI data center.
In distributed training, GPU workers communicate constantly to synchronize gradients. If communicating GPUs are placed across distant servers or cloud zones, network latency explodes and training throughput degrades significantly.
Kubeflow Trainer v2.4 introduces a 3-tier hierarchy to map software jobs to physical hardware topology:

TrainJob Multi-Tier Scheduling Topology across Zone, Block, and Rack domains
Level 1 - TrainJob Level (Zone)
-
Kubernetes Object:
PodGroupin v2.4 (CompositePodGroupplanned) -
Physical Domain: Cloud Zone (
topology.kubernetes.io/zone) - Why it’s needed: Treats the entire training job as a single unit. It ensures all workers are located within the same cloud zone, avoiding cross-zone latency and bandwidth charges. Ideal for uniform workloads (e.g., PyTorch DDP).
-
User Story: Distributed PyTorch Training with Gang Scheduling:
As a platform engineer, I want to run a distributed PyTorch TrainJob with 100 nodes that must all be scheduled together. If only 99 workers can be scheduled, no pods should start.
1. ClusterTrainingRuntime and TrainJob Definitions
apiVersion: trainer.kubeflow.org/v1alpha1
kind: ClusterTrainingRuntime
metadata:
name: torch-distributed
spec:
mlPolicy:
numNodes: 1
torch: {}
scheduling: {} # Enable workload-aware scheduling!
template:
spec:
replicatedJobs:
- name: node
template:
metadata:
labels:
trainer.kubeflow.org/trainjob-ancestor-step: trainer
spec:
template:
spec:
containers:
- name: node
image: pytorch/pytorch:2.15.0-cuda13.4-cudnn9-runtime
---
apiVersion: trainer.kubeflow.org/v1alpha1
kind: TrainJob
metadata:
name: my-job
spec:
runtimeRef:
name: torch-distributed
trainer:
image: docker.io/torch-run
numNodes: 100
resourcesPerNode:
requests:
nvidia.com/gpu: 4
2. Workload Resource Created by TrainJob Controller
apiVersion: scheduling.k8s.io/v1beta1
kind: Workload
metadata:
name: my-job-<hash>
ownerReferences:
- apiVersion: trainer.kubeflow.org/v1alpha1
kind: TrainJob
name: my-job
controller: true
spec:
controllerRef:
apiGroup: trainer.kubeflow.org
kind: TrainJob
name: my-job
podGroupTemplates:
- name: my-job
schedulingPolicy:
gang:
minCount: 100 # Computed from trainJob.spec.trainer.numNodes
3. Automatically Generated PodGroup by the TrainJob controller
apiVersion: scheduling.k8s.io/v1beta1
kind: PodGroup
metadata:
name: <workload-name>-<hash>
ownerReferences:
- apiVersion: trainer.kubeflow.org/v1alpha1
kind: TrainJob
name: my-job
controller: true
spec:
workloadRef:
workloadName: <workload-name>
templateName: my-job
schedulingPolicy:
gang:
minCount: 100
4. Resulting Pod Spec Association
spec:
schedulingGroup:
podGroupName: <workload-name>-<hash>
Level 2 - ReplicatedJob Level (Network Block)
-
Kubernetes Object:
PodGroupin v2.4 (CompositePodGroupplanned) - Physical Domain: Network Block / Spine Switch
- Why it’s needed: Prevents dependency deadlocks in multi-role pipelines. It allows platform engineers to give data initializers a basic policy (so they can run lazily on cheap nodes to download datasets), while allocating a coordinated gang policy for GPU workers once data is ready.
- User Story:
apiVersion: trainer.kubeflow.org/v1alpha1
kind: ClusterTrainingRuntime
metadata:
name: deepspeed-distributed
spec:
mlPolicy:
numNodes: 1
mpi:
numProcPerNode: 1
scheduling:
replicatedJobs:
# The dataset initializer doesn't need GPUs, so it's scheduled individually.
- targetReplicatedJobs: ["dataset-initializer"]
schedulingPolicy:
basic: {}
# MPI launcher and workers are scheduled as one gang within the same zone.
- targetReplicatedJobs: ["launcher", "node"]
schedulingPolicy:
gang: {}
schedulingConstraints:
topology:
- key: topology.kubernetes.io/zone
template:
spec:
replicatedJobs:
- name: dataset-initializer
...
- name: node
dependsOn:
- name: dataset-initializer
status: Complete
...
- name: launcher
dependsOn:
- name: node
status: Ready
Level 3 - Job / Per-Replica Level (Rack-Level)
-
Kubernetes Object:
PodGroup -
Physical Domain: Rack / Top-of-Rack Switch (
topology.kubernetes.io/rack) - Why it’s needed: Maximum network throughput and failure isolation. Workers under the same Top-of-Rack (ToR) switch communicate at fast, non-blocking speeds. Each Job can also get its own DRA claims and failure boundary, the building block for Multi-Slice architectures (like TPU slices). In v2.4, the targeted ReplicatedJob can have at most 1 replica, since per-replica PodGroups require Kubernetes v1.38.
Given the above requirements, the TrainJob topology can be represented using the following WAS building blocks for each Level.

Kubeflow Trainer v2.4 creates PodGroups at the level you choose
In v2.4, Trainer creates only PodGroups, and each ReplicatedJob is scheduled at exactly one level. Platform admins choose the level in the runtime’s spec.scheduling.
Introducing the Three Levels in TrainJob Topology
| Level | Configured In | PodGroups Created | When to Use |
|---|---|---|---|
| Level 1: TrainJob | scheduling |
One PodGroup for all Pods of the TrainJob
|
All Pods must be scheduled, placed, and preempted as one unit (e.g., PyTorch DDP). |
| Level 2: ReplicatedJob | scheduling.replicatedJobs[] |
One PodGroup per entry |
ReplicatedJobs need different policies (e.g., basic policy for data initializers and gang policy for GPU workers). |
| Level 3: Job | scheduling.replicatedJobs[].job |
One PodGroup per Job |
Each Job needs its own gang, topology constraint, or DRA resourceClaims
|
Each PodGroup is independent, so today the hierarchy has a single layer of PodGroups with no parent above them. That is already enough to solve both problems from the introduction:
-
Idle Resource Trap: A gang
PodGroupadmits all of its workers or none, so GPUs are never held while the job waits for the last worker. -
Dependency Deadlock: At Level 2, the initializers get a basic
PodGroupand start right away. The GPU workers wait in their own gang until all of them can be scheduled.
Adopting CompositePodGroup - ROADMAP
CompositePodGroup is alpha in Kubernetes 1.37. Once Trainer adopts it, Levels 1 and 2 become CPGs, Level 3 stays a PodGroup, and the levels can be combined instead of choosing just one, as the diagram shows. This enables:
-
Gang of Gangs: A TrainJob-level CPG admits several Job-level gangs together or not at all. An example is multiple TPU slices, each with its own DRA claims.
-
Nested Topologies: Each level keeps its own placement constraint. For example, the whole
TrainJobstays in one zone, eachReplicatedJobin one network block, and each Job in one rack. -
Mixed Policies in One Tree: The initializers keep a
basicpolicy, while a parent gang keeps the GPU worker gangs together.
In short: today you choose between one big gang and several independent ones. With CPG, Trainer can express “all of these gangs, together, close to each other”.
To learn more about Workload-Aware Scheduling in Trainer, check out the docs and follow the progress in KEP-3015.
Other Feature Highlights in Trainer v2.4
Beyond Workload-Aware Scheduling, Kubeflow Trainer v2.4 includes:
-
Per-TrainJob
schedulerNameoverrides (#4265) - Job replicas for multi-slice TPU training (#4250)
- Secured metrics endpoint with TLS and RBAC-based auth (#3912)
- Prometheus monitoring overlay (#4102)
- PyTorch 2.15 and CUDA 13.4 in the default runtimes (#4369)
Dynamic Resource Allocation (DRA)
Trainer 2.4 adds support for attaching accelerators and other specialised hardware to TrainJobs using Kubernetes Dynamic Resource Allocation (DRA) API (GA in kubernetes 1.34).
-
DRA provides a more flexible way to request and attach hardware to TrainJobs, complementing traditional extended resource requests such as
nvidia.com/gpu: 1. Unlike extended resources, DRA supports more advanced device selection, sharing, and allocation policies, giving users greater control over how hardware is assigned to training workloads. -
To learn more about using DRA in Kubeflow Trainer, check out the Dynamic Resource Allocation (DRA) docs.
OptimizationJob Alpha Integrations
-
Declarative HPO: Enables the
OptimizationJob->TrainJobhierarchy, leveraging TrainJob’s native progress tracking to eliminate the legacy Katib DB dependency. -
Getting Started: Enable the
OptimizationJobandTrainJobStatusalpha feature gates (#4331) and follow the OptimizationJob user guide. -
CEL-Native Validation: Implements server-side CEL validation for
SearchSpaceandTrialConfigconstraints, ensuring high-quality hyperparameter search configurations at the API layer. - OptimizationJob KEP-3562
Release Notes: Kubeflow Trainer v2.4.0 Changelog
Breaking Changes & Upgrade Notices (v2.4)
-
ReplicatedJob Name Length at 20 Characters (#4391)
TrainingRuntimesandClusterTrainingRuntimeswithReplicatedJobnames longer than 20 characters are rejected on creation and on updates that modifyspec.template. -
CertManagement Configuration Fields Renamed (#3386)
Renames theCertManagementconfiguration fields from webhook-specific names to generic names, since the TLS certificate is used by the metrics server and the status server in addition to webhooks. -
TrainJob image synchronization for MPI Runtimes (#4301)
For MPI runtimes,Trainjob spec.trainer.imagenow configures the image for both the worker nodes and the launcher pod. Previously it only configured the launcher pod. -
Helm manifests retain CRDs on uninstall (#4122)
Helm charts now keep the Custom Resource Definitions (CRDs) onhelm uninstall(crds.keep: trueby default), since deleting a CRD deletes every TrainJob, TrainingRuntime, ClusterTrainingRuntime, and OptimizationJob in the cluster. Setcrds.keep=falseto remove the CRDs with the release. -
Enabling Data Cache in Helm Chart no longer automatically installs LWS subchart (#4120)
When data cache is enabled, the Helm chart no longer automatically installs the LeaderWorkerSet (LWS) dependency. Users who rely on the chart to install LWS must explicitly setdataCache.lws.install=true. This allows users with an existing LWS installation to enable data caching without installing LWS again. -
MPI runtimes emit PRTE env vars for Open MPI 5 (#4029)
Suspended TrainJobs that use the MPI plugin (e.g., DeepSpeed, MLX) and were created before the upgrade fail to resume after upgrading to v2.4, because the newPRTE_MCA_*env vars can’t be applied to the existing immutable JobSet. Wait for MPI-based TrainJobs to complete before upgrading, or recreate them after the upgrade.
References
Join the Community
The Kubeflow Trainer is built by and for the community. We welcome contributions, feedback, and participation from everyone! We want to thank the community for their contributions to this release. We invite you to:
Contribute:
- Read the Contributing Guide.
- Browse the good first issues
- Explore the GitHub Repository
Connect with the Community:
- Join #kubeflow-trainer on CNCF Slack
- Attend our biweekly Kubeflow Trainer meetings
Learn More:
- View the GitHub Release
- Explore the Kubeflow Trainer docs