diff --git a/docs/proposals/2170-kubeflow-training-v2/README.md b/docs/proposals/2170-kubeflow-training-v2/README.md index 81ba10ecad..cb829345ab 100644 --- a/docs/proposals/2170-kubeflow-training-v2/README.md +++ b/docs/proposals/2170-kubeflow-training-v2/README.md @@ -337,17 +337,16 @@ type TrainJobStatus struct { // Conditions for the TrainJob. Conditions []metav1.Condition `json:"conditions,omitempty"` - // ReplicatedJobsStatus tracks the number of Jobs for each replicatedJob in TrainJob. - ReplicatedJobsStatus []jobsetv1alpha2.ReplicatedJobStatus `json:"replicatedJobsStatus,omitempty"` + // JobsStatus tracks the child Jobs in TrainJob. + JobsStatus []JobStatus `json:"jobsStatus,omitempty"` } -type ReplicatedJobStatus struct { - // Name of the ReplicatedJob. +type JobStatus struct { + // Name of the child Job. Name string `json:"name"` // Ready is the number of child Jobs where the number of ready pods and completed pods - // is greater than or equal to the total expected pod count for the Job (i.e., the minimum - // of job.spec.parallelism and job.spec.completions). + // is greater than or equal to the total expected pod count for the child Job. Ready int32 `json:"ready"` // Succeeded is the number of successfully completed child Jobs. @@ -831,8 +830,8 @@ In the future, we can add more parameters if we find use-cases when it is requir ```golang type PodSpecOverride struct { - // Names of the training job replicas in the training runtime template to apply the overrides. - TargetReplicatedJobs []string `json:"targetReplicatedJobs"` + // TrainJobs is the training job replicas in the training runtime template to apply the overrides. + TargetJobs []PodSpecOverrideTargetJob `json:"targetJobs"` // Overrides for the containers in the desired job templates. Containers []ContainerOverride `json:"containers,omitempty"` @@ -853,6 +852,11 @@ type PodSpecOverride struct { Tolerations []corev1.Toleration `json:"tolerations,omitempty"` } +type PodSpecOverrideTargetJob struct { + // Name is the target training job name for which the PodSpec is overridden. + Name string `json:"name"` +} + // ContainerOverride represents parameters that can be overridden using PodSpecOverride. // Parameters from the Trainer, DatasetConfig, and ModelConfig will take precedence. type ContainerOverride struct { @@ -895,8 +899,8 @@ spec: trainer: image: docker.io/custom-training podSpecOverrides: - - targetReplicatedJobs: - - node + - targetJobs: + - name: node containers: - name: user-identity value: 123 diff --git a/hack/violation_exception_v2alpha1.list b/hack/violation_exception_v2alpha1.list index 42ef0a81d8..b636df625d 100644 --- a/hack/violation_exception_v2alpha1.list +++ b/hack/violation_exception_v2alpha1.list @@ -8,13 +8,13 @@ API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/ API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,OutputModel,Env API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,PodSpecOverride,Containers API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,PodSpecOverride,InitContainers -API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,PodSpecOverride,TargetReplicatedJobs +API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,PodSpecOverride,TargetJobs API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,PodSpecOverride,Tolerations API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,PodSpecOverride,Volumes API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,TorchElasticPolicy,Metrics API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,TrainJobSpec,PodSpecOverrides API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,TrainJobStatus,Conditions -API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,TrainJobStatus,ReplicatedJobsStatus +API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,TrainJobStatus,JobsStatus API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,Trainer,Args API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,Trainer,Command API rule violation: list_type_missing,github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1,Trainer,Env diff --git a/manifests/v2/base/crds/kubeflow.org_trainjobs.yaml b/manifests/v2/base/crds/kubeflow.org_trainjobs.yaml index b5583cbaaf..feb9fdaee8 100644 --- a/manifests/v2/base/crds/kubeflow.org_trainjobs.yaml +++ b/manifests/v2/base/crds/kubeflow.org_trainjobs.yaml @@ -508,7 +508,7 @@ spec: templates. items: description: |- - ContainerOverrides represents parameters that can be overridden using PodSpecOverrides. + ContainerOverride represents parameters that can be overridden using PodSpecOverrides. Parameters from the Trainer, DatasetConfig, and ModelConfig will take precedence. properties: args: @@ -740,7 +740,7 @@ spec: job templates. items: description: |- - ContainerOverrides represents parameters that can be overridden using PodSpecOverrides. + ContainerOverride represents parameters that can be overridden using PodSpecOverrides. Parameters from the Trainer, DatasetConfig, and ModelConfig will take precedence. properties: args: @@ -976,11 +976,18 @@ spec: serviceAccountName: description: Override for the service account. type: string - targetReplicatedJobs: - description: Names of the training job replicas in the training + targetJobs: + description: TrainJobs is the training job replicas in the training runtime template to apply the overrides. items: - type: string + properties: + name: + description: Name is the target training job name for + which the PodSpec is overridden. + type: string + required: + - name + type: object type: array tolerations: description: Override for the Pod's tolerations. @@ -2736,7 +2743,7 @@ spec: type: object type: array required: - - targetReplicatedJobs + - targetJobs type: object type: array runtimeRef: @@ -3045,12 +3052,9 @@ spec: - type type: object type: array - replicatedJobsStatus: - description: ReplicatedJobsStatus tracks the number of Jobs for each - replicatedJob in TrainJob. + jobsStatus: + description: JobsStatus tracks the child Jobs in TrainJob. items: - description: ReplicatedJobStatus defines the observed ReplicatedJobs - Readiness. properties: active: description: |- @@ -3063,13 +3067,12 @@ spec: format: int32 type: integer name: - description: Name of the ReplicatedJob. + description: Name of the child Job. type: string ready: description: |- Ready is the number of child Jobs where the number of ready pods and completed pods - is greater than or equal to the total expected pod count for the Job (i.e., the minimum - of job.spec.parallelism and job.spec.completions). + is greater than or equal to the total expected pod count for the child Job. format: int32 type: integer succeeded: diff --git a/pkg/apis/kubeflow.org/v2alpha1/openapi_generated.go b/pkg/apis/kubeflow.org/v2alpha1/openapi_generated.go index 0eeb05ddd0..5c91173a72 100644 --- a/pkg/apis/kubeflow.org/v2alpha1/openapi_generated.go +++ b/pkg/apis/kubeflow.org/v2alpha1/openapi_generated.go @@ -35,6 +35,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.DatasetConfig": schema_pkg_apis_kubefloworg_v2alpha1_DatasetConfig(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.InputModel": schema_pkg_apis_kubefloworg_v2alpha1_InputModel(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.JobSetTemplateSpec": schema_pkg_apis_kubefloworg_v2alpha1_JobSetTemplateSpec(ref), + "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.JobStatus": schema_pkg_apis_kubefloworg_v2alpha1_JobStatus(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.MLPolicy": schema_pkg_apis_kubefloworg_v2alpha1_MLPolicy(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.MLPolicySource": schema_pkg_apis_kubefloworg_v2alpha1_MLPolicySource(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.MPIMLPolicySource": schema_pkg_apis_kubefloworg_v2alpha1_MPIMLPolicySource(ref), @@ -43,6 +44,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.PodGroupPolicy": schema_pkg_apis_kubefloworg_v2alpha1_PodGroupPolicy(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.PodGroupPolicySource": schema_pkg_apis_kubefloworg_v2alpha1_PodGroupPolicySource(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.PodSpecOverride": schema_pkg_apis_kubefloworg_v2alpha1_PodSpecOverride(ref), + "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.PodSpecOverrideTargetJob": schema_pkg_apis_kubefloworg_v2alpha1_PodSpecOverrideTargetJob(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.RuntimeRef": schema_pkg_apis_kubefloworg_v2alpha1_RuntimeRef(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.TorchElasticPolicy": schema_pkg_apis_kubefloworg_v2alpha1_TorchElasticPolicy(ref), "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.TorchMLPolicySource": schema_pkg_apis_kubefloworg_v2alpha1_TorchMLPolicySource(ref), @@ -155,7 +157,7 @@ func schema_pkg_apis_kubefloworg_v2alpha1_ContainerOverride(ref common.Reference return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ContainerOverrides represents parameters that can be overridden using PodSpecOverrides. Parameters from the Trainer, DatasetConfig, and ModelConfig will take precedence.", + Description: "ContainerOverride represents parameters that can be overridden using PodSpecOverrides. Parameters from the Trainer, DatasetConfig, and ModelConfig will take precedence.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "name": { @@ -380,6 +382,67 @@ func schema_pkg_apis_kubefloworg_v2alpha1_JobSetTemplateSpec(ref common.Referenc } } +func schema_pkg_apis_kubefloworg_v2alpha1_JobStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the child Job.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "ready": { + SchemaProps: spec.SchemaProps{ + Description: "Ready is the number of child Jobs where the number of ready pods and completed pods is greater than or equal to the total expected pod count for the child Job.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "succeeded": { + SchemaProps: spec.SchemaProps{ + Description: "Succeeded is the number of successfully completed child Jobs.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "failed": { + SchemaProps: spec.SchemaProps{ + Description: "Failed is the number of failed child Jobs.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "active": { + SchemaProps: spec.SchemaProps{ + Description: "Active is the number of child Jobs with at least 1 pod in a running or pending state which are not marked for deletion.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "suspended": { + SchemaProps: spec.SchemaProps{ + Description: "Suspended is the number of child Jobs which are in a suspended state.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"name", "ready", "succeeded", "failed", "active", "suspended"}, + }, + }, + } +} + func schema_pkg_apis_kubefloworg_v2alpha1_MLPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -600,16 +663,15 @@ func schema_pkg_apis_kubefloworg_v2alpha1_PodSpecOverride(ref common.ReferenceCa Description: "PodSpecOverride represents the custom overrides that will be applied for the TrainJob's resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "targetReplicatedJobs": { + "targetJobs": { SchemaProps: spec.SchemaProps{ - Description: "Names of the training job replicas in the training runtime template to apply the overrides.", + Description: "TrainJobs is the training job replicas in the training runtime template to apply the overrides.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.PodSpecOverrideTargetJob"), }, }, }, @@ -695,11 +757,32 @@ func schema_pkg_apis_kubefloworg_v2alpha1_PodSpecOverride(ref common.ReferenceCa }, }, }, - Required: []string{"targetReplicatedJobs"}, + Required: []string{"targetJobs"}, }, }, Dependencies: []string{ - "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.ContainerOverride", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, + "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.ContainerOverride", "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.PodSpecOverrideTargetJob", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, + } +} + +func schema_pkg_apis_kubefloworg_v2alpha1_PodSpecOverrideTargetJob(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the target training job name for which the PodSpec is overridden.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, } } @@ -1040,15 +1123,15 @@ func schema_pkg_apis_kubefloworg_v2alpha1_TrainJobStatus(ref common.ReferenceCal }, }, }, - "replicatedJobsStatus": { + "jobsStatus": { SchemaProps: spec.SchemaProps{ - Description: "ReplicatedJobsStatus tracks the number of Jobs for each replicatedJob in TrainJob.", + Description: "JobsStatus tracks the child Jobs in TrainJob.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("sigs.k8s.io/jobset/api/jobset/v1alpha2.ReplicatedJobStatus"), + Ref: ref("github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.JobStatus"), }, }, }, @@ -1058,7 +1141,7 @@ func schema_pkg_apis_kubefloworg_v2alpha1_TrainJobStatus(ref common.ReferenceCal }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "sigs.k8s.io/jobset/api/jobset/v1alpha2.ReplicatedJobStatus"}, + "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v2alpha1.JobStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, } } diff --git a/pkg/apis/kubeflow.org/v2alpha1/trainjob_types.go b/pkg/apis/kubeflow.org/v2alpha1/trainjob_types.go index 3baab1fb35..3e698429fa 100644 --- a/pkg/apis/kubeflow.org/v2alpha1/trainjob_types.go +++ b/pkg/apis/kubeflow.org/v2alpha1/trainjob_types.go @@ -19,7 +19,6 @@ package v2alpha1 import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - jobsetv1alpha2 "sigs.k8s.io/jobset/api/jobset/v1alpha2" ) const ( @@ -210,8 +209,8 @@ type OutputModel struct { // PodSpecOverride represents the custom overrides that will be applied for the TrainJob's resources. type PodSpecOverride struct { - // Names of the training job replicas in the training runtime template to apply the overrides. - TargetReplicatedJobs []string `json:"targetReplicatedJobs"` + // TrainJobs is the training job replicas in the training runtime template to apply the overrides. + TargetJobs []PodSpecOverrideTargetJob `json:"targetJobs"` // Overrides for the containers in the desired job templates. Containers []ContainerOverride `json:"containers,omitempty"` @@ -232,7 +231,12 @@ type PodSpecOverride struct { Tolerations []corev1.Toleration `json:"tolerations,omitempty"` } -// ContainerOverrides represents parameters that can be overridden using PodSpecOverrides. +type PodSpecOverrideTargetJob struct { + // Name is the target training job name for which the PodSpec is overridden. + Name string `json:"name"` +} + +// ContainerOverride represents parameters that can be overridden using PodSpecOverrides. // Parameters from the Trainer, DatasetConfig, and ModelConfig will take precedence. type ContainerOverride struct { // Name for the container. TrainingRuntime must have this container. @@ -261,8 +265,30 @@ type TrainJobStatus struct { // Conditions for the TrainJob. Conditions []metav1.Condition `json:"conditions,omitempty"` - // ReplicatedJobsStatus tracks the number of Jobs for each replicatedJob in TrainJob. - ReplicatedJobsStatus []jobsetv1alpha2.ReplicatedJobStatus `json:"replicatedJobsStatus,omitempty"` + // JobsStatus tracks the child Jobs in TrainJob. + JobsStatus []JobStatus `json:"jobsStatus,omitempty"` +} + +type JobStatus struct { + // Name of the child Job. + Name string `json:"name"` + + // Ready is the number of child Jobs where the number of ready pods and completed pods + // is greater than or equal to the total expected pod count for the child Job. + Ready int32 `json:"ready"` + + // Succeeded is the number of successfully completed child Jobs. + Succeeded int32 `json:"succeeded"` + + // Failed is the number of failed child Jobs. + Failed int32 `json:"failed"` + + // Active is the number of child Jobs with at least 1 pod in a running or pending state + // which are not marked for deletion. + Active int32 `json:"active"` + + // Suspended is the number of child Jobs which are in a suspended state. + Suspended int32 `json:"suspended"` } func init() { diff --git a/pkg/apis/kubeflow.org/v2alpha1/zz_generated.deepcopy.go b/pkg/apis/kubeflow.org/v2alpha1/zz_generated.deepcopy.go index 2c87b2a838..e643a85bdb 100644 --- a/pkg/apis/kubeflow.org/v2alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kubeflow.org/v2alpha1/zz_generated.deepcopy.go @@ -23,7 +23,6 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/jobset/api/jobset/v1alpha2" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -231,6 +230,21 @@ func (in *JobSetTemplateSpec) DeepCopy() *JobSetTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JobStatus) DeepCopyInto(out *JobStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobStatus. +func (in *JobStatus) DeepCopy() *JobStatus { + if in == nil { + return nil + } + out := new(JobStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MLPolicy) DeepCopyInto(out *MLPolicy) { *out = *in @@ -408,9 +422,9 @@ func (in *PodGroupPolicySource) DeepCopy() *PodGroupPolicySource { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodSpecOverride) DeepCopyInto(out *PodSpecOverride) { *out = *in - if in.TargetReplicatedJobs != nil { - in, out := &in.TargetReplicatedJobs, &out.TargetReplicatedJobs - *out = make([]string, len(*in)) + if in.TargetJobs != nil { + in, out := &in.TargetJobs, &out.TargetJobs + *out = make([]PodSpecOverrideTargetJob, len(*in)) copy(*out, *in) } if in.Containers != nil { @@ -460,6 +474,21 @@ func (in *PodSpecOverride) DeepCopy() *PodSpecOverride { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodSpecOverrideTargetJob) DeepCopyInto(out *PodSpecOverrideTargetJob) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpecOverrideTargetJob. +func (in *PodSpecOverrideTargetJob) DeepCopy() *PodSpecOverrideTargetJob { + if in == nil { + return nil + } + out := new(PodSpecOverrideTargetJob) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RuntimeRef) DeepCopyInto(out *RuntimeRef) { *out = *in @@ -678,9 +707,9 @@ func (in *TrainJobStatus) DeepCopyInto(out *TrainJobStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.ReplicatedJobsStatus != nil { - in, out := &in.ReplicatedJobsStatus, &out.ReplicatedJobsStatus - *out = make([]v1alpha2.ReplicatedJobStatus, len(*in)) + if in.JobsStatus != nil { + in, out := &in.JobsStatus, &out.JobsStatus + *out = make([]JobStatus, len(*in)) copy(*out, *in) } }