Skip to content

Commit

Permalink
Adding cel validation on trainingRuntime CRD
Browse files Browse the repository at this point in the history
Signed-off-by: Akshay Chitneni <achitneni@apple.com>
  • Loading branch information
Akshay Chitneni committed Nov 11, 2024
1 parent 94dee0e commit d023258
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 0 deletions.
13 changes: 13 additions & 0 deletions manifests/v2/base/crds/kubeflow.org_clustertrainingruntimes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ spec:
description: Directory where SSH keys are mounted.
type: string
mpiImplementation:
default: OpenMPI
description: |-
Implementation name for the MPI to create the appropriate hostfile.
Defaults to OpenMPI.
Expand All @@ -64,6 +65,7 @@ spec:
format: int32
type: integer
runLauncherAsNode:
default: false
description: |-
Whether to run training process on the launcher Job.
Defaults to false.
Expand Down Expand Up @@ -576,15 +578,25 @@ spec:
type: integer
type: object
numProcPerNode:
default: auto
description: |-
Number of processes per node.
This value is inserted into the `--nproc-per-node` argument of the `torchrun` CLI.
Supported values: `auto`, `cpu`, `gpu`, or int value.
TODO (andreyvelich): Add kubebuilder validation.
Defaults to `auto`.
type: string
x-kubernetes-validations:
- message: NumProcPerNode must be auto,cpu,gpu strings or
int value
rule: self in ['auto', 'cpu', 'gpu'] || type(self) == int
type: object
type: object
x-kubernetes-validations:
- message: numNodes should not be set if torch.elasticPolicy is configured
rule: '!(has(self.numNodes) && (has(self.torch) && has(self.torch.elasticPolicy)))'
- message: Only one of the policy can be configured
rule: '!(has(self.torch) && has(self.mpi))'
podGroupPolicy:
description: Configuration for the PodGroup to enable gang-scheduling
via supported plugins.
Expand All @@ -594,6 +606,7 @@ spec:
for gang-scheduling.
properties:
scheduleTimeoutSeconds:
default: 60
description: |-
Time threshold to schedule PodGroup for gang-scheduling.
If the scheduling timeout is equal to 0, the default value is used.
Expand Down
13 changes: 13 additions & 0 deletions manifests/v2/base/crds/kubeflow.org_trainingruntimes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ spec:
description: Directory where SSH keys are mounted.
type: string
mpiImplementation:
default: OpenMPI
description: |-
Implementation name for the MPI to create the appropriate hostfile.
Defaults to OpenMPI.
Expand All @@ -64,6 +65,7 @@ spec:
format: int32
type: integer
runLauncherAsNode:
default: false
description: |-
Whether to run training process on the launcher Job.
Defaults to false.
Expand Down Expand Up @@ -576,15 +578,25 @@ spec:
type: integer
type: object
numProcPerNode:
default: auto
description: |-
Number of processes per node.
This value is inserted into the `--nproc-per-node` argument of the `torchrun` CLI.
Supported values: `auto`, `cpu`, `gpu`, or int value.
TODO (andreyvelich): Add kubebuilder validation.
Defaults to `auto`.
type: string
x-kubernetes-validations:
- message: NumProcPerNode must be auto,cpu,gpu strings or
int value
rule: self in ['auto', 'cpu', 'gpu'] || type(self) == int
type: object
type: object
x-kubernetes-validations:
- message: numNodes should not be set if torch.elasticPolicy is configured
rule: '!(has(self.numNodes) && (has(self.torch) && has(self.torch.elasticPolicy)))'
- message: Only one of the policy can be configured
rule: '!(has(self.torch) && has(self.mpi))'
podGroupPolicy:
description: Configuration for the PodGroup to enable gang-scheduling
via supported plugins.
Expand All @@ -594,6 +606,7 @@ spec:
for gang-scheduling.
properties:
scheduleTimeoutSeconds:
default: 60
description: |-
Time threshold to schedule PodGroup for gang-scheduling.
If the scheduling timeout is equal to 0, the default value is used.
Expand Down
7 changes: 7 additions & 0 deletions pkg/apis/kubeflow.org/v2alpha1/trainingruntime_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,13 @@ type CoschedulingPodGroupPolicySource struct {
// Time threshold to schedule PodGroup for gang-scheduling.
// If the scheduling timeout is equal to 0, the default value is used.
// Defaults to 60 seconds.
// +kubebuilder:default=60
ScheduleTimeoutSeconds *int32 `json:"scheduleTimeoutSeconds,omitempty"`
}

// MLPolicy represents configuration for the model trining with ML-specific parameters.
// +kubebuilder:validation:XValidation:rule="!(has(self.numNodes) && (has(self.torch) && has(self.torch.elasticPolicy)))", message="numNodes should not be set if torch.elasticPolicy is configured"
// +kubebuilder:validation:XValidation:rule="!(has(self.torch) && has(self.mpi))", message="Only one of the policy can be configured"
type MLPolicy struct {
// Number of training nodes.
// Defaults to 1.
Expand Down Expand Up @@ -173,6 +176,8 @@ type TorchMLPolicySource struct {
// Supported values: `auto`, `cpu`, `gpu`, or int value.
// TODO (andreyvelich): Add kubebuilder validation.
// Defaults to `auto`.
// +kubebuilder:default="auto"
// +kubebuilder:validation:XValidation:rule="self in ['auto', 'cpu', 'gpu'] || type(self) == int", message="NumProcPerNode must be auto,cpu,gpu strings or int value"
NumProcPerNode *string `json:"numProcPerNode,omitempty"`

// Elastic policy for the PyTorch training.
Expand Down Expand Up @@ -209,13 +214,15 @@ type MPIMLPolicySource struct {

// Implementation name for the MPI to create the appropriate hostfile.
// Defaults to OpenMPI.
// +kubebuilder:default="OpenMPI"
MPIImplementation *MPIImplementation `json:"mpiImplementation,omitempty"`

// Directory where SSH keys are mounted.
SSHAuthMountPath *string `json:"SSHAuthMountPath,omitempty"`

// Whether to run training process on the launcher Job.
// Defaults to false.
// +kubebuilder:default=false
RunLauncherAsNode *bool `json:"runLauncherAsNode,omitempty"`
}

Expand Down
128 changes: 128 additions & 0 deletions test/integration/controller.v2/trainingruntime_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
Copyright 2024 The Kubeflow Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllerv2

import (
kubeflowv2 "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1"
testingutil "github.com/kubeflow/training-operator/pkg/util.v2/testing"
"github.com/kubeflow/training-operator/test/integration/framework"
"github.com/kubeflow/training-operator/test/util"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = ginkgo.Describe("TrainingRuntime marker validations and defaulting", ginkgo.Ordered, func() {
var ns *corev1.Namespace

ginkgo.BeforeAll(func() {
fwk = &framework.Framework{}
cfg = fwk.Init()
ctx, k8sClient = fwk.RunManager(cfg)
})
ginkgo.AfterAll(func() {
fwk.Teardown()
})

ginkgo.BeforeEach(func() {
ns = &corev1.Namespace{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Namespace",
},
ObjectMeta: metav1.ObjectMeta{
GenerateName: "training-runtime-marker-",
},
}
gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed())
})
ginkgo.AfterEach(func() {
gomega.Expect(k8sClient.DeleteAllOf(ctx, &kubeflowv2.TrainingRuntime{}, client.InNamespace(ns.Name))).Should(gomega.Succeed())
gomega.Expect(k8sClient.DeleteAllOf(ctx, &kubeflowv2.ClusterTrainingRuntime{})).Should(gomega.Succeed())
})

ginkgo.When("Creating TrainingRuntime", func() {
ginkgo.DescribeTable("Validate TrainingRuntime on creation", func(trainingRuntime func() *kubeflowv2.TrainingRuntime, errorMatcher gomega.OmegaMatcher) {
gomega.Expect(k8sClient.Create(ctx, trainingRuntime())).Should(errorMatcher)
},
ginkgo.Entry("Should succeed to create trainingRuntime",
func() *kubeflowv2.TrainingRuntime {
return testingutil.MakeTrainingRuntimeWrapper(ns.Name, "runtime").
Obj()
},
gomega.Succeed()),
ginkgo.Entry("Should fail to create trainingRuntime with both MPI and Torch runtimes",
func() *kubeflowv2.TrainingRuntime {
runtime := testingutil.MakeTrainingRuntimeWrapper(ns.Name, "runtime").Obj()
runtime.Spec.MLPolicy = &kubeflowv2.MLPolicy{
MLPolicySource: kubeflowv2.MLPolicySource{
Torch: &kubeflowv2.TorchMLPolicySource{},
MPI: &kubeflowv2.MPIMLPolicySource{},
},
}
return runtime
},
testingutil.BeInvalidError()),
ginkgo.Entry("Should fail to create trainingRuntime with minNodes and torch.elasticPolicy",
func() *kubeflowv2.TrainingRuntime {
runtime := testingutil.MakeTrainingRuntimeWrapper(ns.Name, "runtime").Obj()
runtime.Spec.MLPolicy = &kubeflowv2.MLPolicy{
NumNodes: ptr.To(int32(2)),
MLPolicySource: kubeflowv2.MLPolicySource{
Torch: &kubeflowv2.TorchMLPolicySource{
ElasticPolicy: &kubeflowv2.TorchElasticPolicy{},
},
},
}
return runtime
},
testingutil.BeInvalidError()),
)
ginkgo.DescribeTable("Defaulting TrainingRuntime on creation", func(trainingRuntime func() *kubeflowv2.TrainingRuntime, wantTrainingRuntime func() *kubeflowv2.TrainingRuntime) {
created := trainingRuntime()
gomega.Expect(k8sClient.Create(ctx, created)).Should(gomega.Succeed())
gomega.Expect(created).Should(gomega.BeComparableTo(wantTrainingRuntime(), util.IgnoreObjectMetadata))
},
ginkgo.Entry("Should succeed to default TorchMLPolicySource.NumProcPerNode=auto",
func() *kubeflowv2.TrainingRuntime {
runtime := testingutil.MakeTrainingRuntimeWrapper(ns.Name, "runtime").Obj()
runtime.Spec.MLPolicy = &kubeflowv2.MLPolicy{
MLPolicySource: kubeflowv2.MLPolicySource{
Torch: &kubeflowv2.TorchMLPolicySource{},
},
}
return runtime
},
func() *kubeflowv2.TrainingRuntime {
runtime := testingutil.MakeTrainingRuntimeWrapper(ns.Name, "runtime").Obj()
runtime.Spec.MLPolicy = &kubeflowv2.MLPolicy{
MLPolicySource: kubeflowv2.MLPolicySource{
Torch: &kubeflowv2.TorchMLPolicySource{
NumProcPerNode: ptr.To("auto"),
},
},
}
runtime.Spec.Template.Spec = testingutil.MakeJobSetWrapper(ns.Name, "runtime").
Replicas(1).Obj().Spec
return runtime
}),
)
})
})

0 comments on commit d023258

Please sign in to comment.