Skip to content

fix(framework): BREAKING CHANGE: Set the TrainJob image on the MPI launcher and nodes - #4301

Open
matbun wants to merge 9 commits into
kubeflow:masterfrom
matbun:trainer-image-plugins
Open

matbun wants to merge 9 commits into
kubeflow:masterfrom
matbun:trainer-image-plugins

Conversation

@matbun

@matbun matbun commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Follow up of the discussion in #4050. The JobSet builder set spec.trainer.image on the node container of the replicatedJob with the trainer ancestor label, the same container for every framework. This PR moves it to EnforceMLPolicy: each MLPolicy plugin sets the image on its trainer containers with SetContainerImageFromTrainer, and the JobSet plugin syncs it into the JobSet like the other PodSet fields.

Torch, JAX, XGBoost, Flux and runtimes without an MLPolicy keep their current behaviour. The MPI plugin now sets the image on both the launcher and the nodes, so all MPI processes run the same application and MPI versions, while command and args still reach only the launcher and the nodes keep running the SSH server. MPI runtimes whose nodes used a different image than the TrainJob now run the TrainJob image on the nodes too, so the migration guide is updated. This supersedes #4050.

Tested with the unit tests and with the controller and webhook integration tests.

Which issue(s) this PR fixes (optional, in Fixes #<issue number>, #<issue number>, ... format, will close the issue(s) when PR gets merged):
Fixes #4030

Checklist:

  • Docs included if any changes are user facing

The JobSet builder set spec.trainer.image on the node container of the trainer ancestor replicatedJob for every framework. Move it to EnforceMLPolicy, where each MLPolicy plugin sets the image on its trainer PodSet container, so a plugin can decide which containers run the TrainJob image. PlainML covers XGBoost, Flux and runtimes without an MLPolicy, as it does for env. No behaviour change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>
@google-oss-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign andreyvelich for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@andreyvelich andreyvelich left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/ok-to-test

The MPI plugin now also sets spec.trainer.image on the node container of the node replicatedJob, so the MPI processes run the same image as mpirun on the launcher. command and args still reach only the launcher, so the nodes keep running sshd. Other frameworks are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>
@matbun
matbun marked this pull request as ready for review October 23, 2026 21:33
@google-oss-prow
google-oss-prow Bot requested a review from jinchihe October 23, 2026 21:33
// The TrainJob image runs mpirun on the launcher, the trainer step, and the MPI processes
// on the nodes, which must ship the same application and MPI versions as the launcher.
runtime.ApplyTrainerImage(info.FindContainerByPodSetAncestorContainerName(constants.AncestorTrainer, constants.Node), trainJob.Spec.Trainer)
runtime.ApplyTrainerImage(info.FindContainerByPodSetName(constants.Node, constants.Node), trainJob.Spec.Trainer)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant for #4030

Comment thread pkg/runtime/runtime.go Outdated
Comment on lines +207 to +210
// ApplyTrainerImage sets the image of the given trainer container from the TrainJob trainer.
// It is called by the MLPolicy plugins in EnforceMLPolicy, so each plugin decides which
// containers run the TrainJob image.
func ApplyTrainerImage(container *Container, jobTrainer *trainer.Trainer) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ApplyTrainerImage sets the image of the given trainer container from the TrainJob trainer.
// It is called by the MLPolicy plugins in EnforceMLPolicy, so each plugin decides which
// containers run the TrainJob image.
func ApplyTrainerImage(container *Container, jobTrainer *trainer.Trainer) {
// SetContainerImageFromTrainer sets the container image from the TrainJob trainer image.
// It is a no-op if the container is nil or the TrainJob trainer image is not set.
func SetContainerImageFromTrainer(container *Container, jobTrainer *trainer.Trainer) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return nil
}

// The TrainJob image runs mpirun on the launcher, the trainer step, and the MPI processes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andreyvelich andreyvelich Oct 23, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and XGBoost

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done for both

Scheduler: &runtime.Scheduler{PodLabels: make(map[string]string)},
},
},
"trainJob trainer image is set on the trainer container of XGBoost runtimes": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go to XGBoost plugin, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, removed it from the PlainML tests. The XGBoost tests already cover it: XGBoost training with GPU resources, resources set in TrainJob only and resources set in both Runtime and TrainJob now expect the TrainJob image on the trainer container

}
}

func TestMPITrainerImage(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, removed TestMPITrainerImage and moved the image into TestMPI.

Rename ApplyTrainerImage to SetContainerImageFromTrainer, and set the TrainJob image on the trainer container in the Flux and XGBoost plugins too, instead of relying on the PlainML fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>
Comment on lines +124 to +127
// The TrainJob image runs mpirun on the launcher, the trainer step, and the MPI processes
// on the nodes, which must ship the same application and MPI versions as the launcher.
runtime.SetContainerImageFromTrainer(info.FindContainerByPodSetAncestorContainerName(constants.AncestorTrainer, constants.Node), trainJob.Spec.Trainer)
runtime.SetContainerImageFromTrainer(info.FindContainerByPodSetName(constants.Node, constants.Node), trainJob.Spec.Trainer)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look up both PodSets by name, consistent with the rest of the MPI plugin (e.g. the volumes loop below):

Suggested change
// The TrainJob image runs mpirun on the launcher, the trainer step, and the MPI processes
// on the nodes, which must ship the same application and MPI versions as the launcher.
runtime.SetContainerImageFromTrainer(info.FindContainerByPodSetAncestorContainerName(constants.AncestorTrainer, constants.Node), trainJob.Spec.Trainer)
runtime.SetContainerImageFromTrainer(info.FindContainerByPodSetName(constants.Node, constants.Node), trainJob.Spec.Trainer)
// Set the TrainJob image on the launcher and the nodes, since mpirun on the launcher starts
// the MPI processes on the nodes, which must ship the same application and MPI versions.
for _, psName := range []string{constants.Launcher, constants.Node} {
runtime.SetContainerImageFromTrainer(info.FindContainerByPodSetName(psName, constants.Node), trainJob.Spec.Trainer)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applied, thanks!

// The TrainJob image runs mpirun on the launcher, the trainer step, and the MPI processes
// on the nodes, which must ship the same application and MPI versions as the launcher.
runtime.SetContainerImageFromTrainer(info.FindContainerByPodSetAncestorContainerName(constants.AncestorTrainer, constants.Node), trainJob.Spec.Trainer)
runtime.SetContainerImageFromTrainer(info.FindContainerByPodSetName(constants.Node, constants.Node), trainJob.Spec.Trainer)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test follow-ups for the node image:

  • When moving the cases into TestMPI, please also cover the runLauncherAsNode: true layout.
  • In the "MPI launcher runs as a node" integration test (test/integration/controller/trainjob_controller_test.go ~L1334), add g.Expect(podSpec.Containers[cIdx].Image).Should(gomega.Equal("test:trainjob")) to verify the node image end to end.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: TestMPI now checks the image on the launcher and the nodes in both the default layout (trainJob numNodes is respected rather than mlPolicy one) and runLauncherAsNode is true, and the launcher as a node integration test asserts the node image.

Comment thread pkg/runtime/framework/plugins/jobset/builder.go Outdated
Comment thread pkg/runtime/framework/plugins/torch/torch.go Outdated
Comment thread pkg/runtime/runtime_test.go
Containers: []runtime.Container{{
Name: constants.Node,
Name: constants.Node,
Image: "test:image",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These runtime containers have no image, so the tests only cover filling an empty image, not replacing one. Please set .WithImage("test:runtime") on the runtime container in at least one case (e.g. L420 here and jax_test.go L422). Similarly, the JAX/XGBoost/OpenMPI/Flux integration fixtures (trainjob_controller_test.go L1998, L2321, L1387, L2664) use test:trainjob for the runtime image too, so they can't detect a missing override.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the runtime containers now use test:runtime in that Torch case, the JAX case, both MPI cases, and the JAX, XGBoost, OpenMPI and Flux integration fixtures, so the tests check that the TrainJob image replaces the runtime one

matbun and others added 5 commits October 24, 2026 00:06
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>
Co-authored-by: Andrey Velichkevich <andrey.velichkevich@gmail.com>
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>
Co-authored-by: Andrey Velichkevich <andrey.velichkevich@gmail.com>
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>
Co-authored-by: Andrey Velichkevich <andrey.velichkevich@gmail.com>
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>
Set the TrainJob image on the MPI launcher and nodes in one loop, as suggested in the review. Cover the image in both the default and the runLauncherAsNode layouts in TestMPI, give the runtime containers their own image in the Torch, JAX and MPI tests and in the JAX, XGBoost, OpenMPI and Flux integration fixtures, so the tests check that the TrainJob image replaces it, and assert the node image in the MPI launcher as a node integration test.

Co-authored-by: Andrey Velichkevich <andrey.velichkevich@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>
@matbun matbun changed the title chore(framework): set the trainer image in the MLPolicy plugins fix(framework): set the TrainJob image in the MLPolicy plugins and on the MPI nodes Oct 23, 2026
…gins

Give the runtime trainer container its own image in the XGBoost and Flux tests, so they check that the TrainJob image replaces it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com>

@andreyvelich andreyvelich left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andreyvelich

Copy link
Copy Markdown
Member

/retitle fix(framework): BREAKING CHANGE: Set the TrainJob image on the MPI launcher and nodes

@google-oss-prow google-oss-prow Bot changed the title fix(framework): set the TrainJob image in the MLPolicy plugins and on the MPI nodes fix(framework): BREAKING CHANGE: Set the TrainJob image on the MPI launcher and nodes Oct 23, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

MPI: spec.trainer.image and spec.trainer.command cannot both be used once the worker job carries the trainer ancestor label

6 participants