Repository: kubernetes-sigs/kueue

Issue:
Allow to merge equivalent PodSets when creating ProvisioningRequest
<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:

Ability to merge similar to identical PodSets into one PodTemplate in ProvisioningRequest.

So the API we could add a new API in ProvisiongRequestConfig, for example
MergePodSets: Equivalent / Identical

Equivalent would allow for small differences in fields which are not impacticting ClusterAutoscaler decisions (like args and commends), but would require identical resources requests>

Identical means that the pod sets are strictly identical.


**Why is this needed**:

To overcome the limitation that some cloud-providers may only allow for provisioning one PodTemplate at the time. In that case we want to allow running PyTorch with two PodTemplates which often use same resources, leader and worker using GPUs.

**Completion requirements**:

This enhancement requires the following artifacts:

- [ ] Design doc
- [ ] API change
- [ ] Docs update

The artifacts should be linked in subsequent comments.


Hints from issue comments:
@mimowo: cc @mwielgus @mwysokin @tenzen-y @mszadkow @mszadkow: /assign

Solution patch:
diff --git a/apis/kueue/v1beta1/provisioningrequestconfig_types.go b/apis/kueue/v1beta1/provisioningrequestconfig_types.go
index f05ae39e5..21cb6d6a4 100644
--- a/apis/kueue/v1beta1/provisioningrequestconfig_types.go
+++ b/apis/kueue/v1beta1/provisioningrequestconfig_types.go
@@ -21,10 +21,15 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+type ProvisioningRequestConfigPodSetMergePolicy string
+
 const (
 	// ProvisioningRequestControllerName is the name used by the Provisioning
 	// Request admission check controller.
 	ProvisioningRequestControllerName = "kueue.x-k8s.io/provisioning-request"
+
+	IdenticalWorkloadSchedulingRequirements ProvisioningRequestConfigPodSetMergePolicy = "IdenticalWorkloadSchedulingRequirements"
+	IdenticalPodTemplates                   ProvisioningRequestConfigPodSetMergePolicy = "IdenticalPodTemplates"
 )
 
 // ProvisioningRequestConfigSpec defines the desired state of ProvisioningRequestConfig
@@ -76,6 +81,13 @@ type ProvisioningRequestConfigSpec struct {
 	//
 	// +optional
 	PodSetUpdates *ProvisioningRequestPodSetUpdates `json:"podSetUpdates,omitempty"`
+
+	// podSetMergePolicy specifies the policy for merging PodSets before being passed
+	// to the cluster autoscaler.
+	//
+	// +optional
+	// +kubebuilder:validation:Enum=IdenticalPodTemplates;IdenticalWorkloadSchedulingRequirements
+	PodSetMergePolicy *ProvisioningRequestConfigPodSetMergePolicy `json:"podSetMergePolicy,omitempty"`
 }
 
 type ProvisioningRequestPodSetUpdates struct {
diff --git a/apis/kueue/v1beta1/zz_generated.deepcopy.go b/apis/kueue/v1beta1/zz_generated.deepcopy.go
index 45c869df9..a44c4d354 100644
--- a/apis/kueue/v1beta1/zz_generated.deepcopy.go
+++ b/apis/kueue/v1beta1/zz_generated.deepcopy.go
@@ -1276,6 +1276,11 @@ func (in *ProvisioningRequestConfigSpec) DeepCopyInto(out *ProvisioningRequestCo
 		*out = new(ProvisioningRequestPodSetUpdates)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.PodSetMergePolicy != nil {
+		in, out := &in.PodSetMergePolicy, &out.PodSetMergePolicy
+		*out = new(ProvisioningRequestConfigPodSetMergePolicy)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProvisioningRequestConfigSpec.
diff --git a/charts/kueue/templates/crd/kueue.x-k8s.io_provisioningrequestconfigs.yaml b/charts/kueue/templates/crd/kueue.x-k8s.io_provisioningrequestconfigs.yaml
index 499f33899..4badc39ae 100644
--- a/charts/kueue/templates/crd/kueue.x-k8s.io_provisioningrequestconfigs.yaml
+++ b/charts/kueue/templates/crd/kueue.x-k8s.io_provisioningrequestconfigs.yaml
@@ -78,6 +78,14 @@ spec:
                   description: Parameters contains all other parameters classes may require.
                   maxProperties: 100
                   type: object
+                podSetMergePolicy:
+                  description: |-
+                    podSetMergePolicy specifies the policy for merging PodSets before being passed
+                    to the cluster autoscaler.
+                  enum:
+                    - IdenticalPodTemplates
+                    - IdenticalWorkloadSchedulingRequirements
+                  type: string
                 podSetUpdates:
                   description: |-
                     podSetUpdates specifies the update of the workload's PodSetUpdates which
diff --git a/client-go/applyconfiguration/kueue/v1beta1/provisioningrequestconfigspec.go b/client-go/applyconfiguration/kueue/v1beta1/provisioningrequestconfigspec.go
index f038108ca..2156d6893 100644
--- a/client-go/applyconfiguration/kueue/v1beta1/provisioningrequestconfigspec.go
+++ b/client-go/applyconfiguration/kueue/v1beta1/provisioningrequestconfigspec.go
@@ -25,11 +25,12 @@ import (
 // ProvisioningRequestConfigSpecApplyConfiguration represents a declarative configuration of the ProvisioningRequestConfigSpec type for use
 // with apply.
 type ProvisioningRequestConfigSpecApplyConfiguration struct {
-	ProvisioningClassName *string                                             `json:"provisioningClassName,omitempty"`
-	Parameters            map[string]kueuev1beta1.Parameter                   `json:"parameters,omitempty"`
-	ManagedResources      []v1.ResourceName                                   `json:"managedResources,omitempty"`
-	RetryStrategy         *ProvisioningRequestRetryStrategyApplyConfiguration `json:"retryStrategy,omitempty"`
-	PodSetUpdates         *ProvisioningRequestPodSetUpdatesApplyConfiguration `json:"podSetUpdates,omitempty"`
+	ProvisioningClassName *string                                                  `json:"provisioningClassName,omitempty"`
+	Parameters            map[string]kueuev1beta1.Parameter                        `json:"parameters,omitempty"`
+	ManagedResources      []v1.ResourceName                                        `json:"managedResources,omitempty"`
+	RetryStrategy         *ProvisioningRequestRetryStrategyApplyConfiguration      `json:"retryStrategy,omitempty"`
+	PodSetUpdates         *ProvisioningRequestPodSetUpdatesApplyConfiguration      `json:"podSetUpdates,omitempty"`
+	PodSetMergePolicy     *kueuev1beta1.ProvisioningRequestConfigPodSetMergePolicy `json:"podSetMergePolicy,omitempty"`
 }
 
 // ProvisioningRequestConfigSpecApplyConfiguration constructs a declarative configuration of the ProvisioningRequestConfigSpec type for use with
@@ -85,3 +86,11 @@ func (b *ProvisioningRequestConfigSpecApplyConfiguration) WithPodSetUpdates(valu
 	b.PodSetUpdates = value
 	return b
 }
+
+// WithPodSetMergePolicy sets the PodSetMergePolicy field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the PodSetMergePolicy field is set to the value of the last call.
+func (b *ProvisioningRequestConfigSpecApplyConfiguration) WithPodSetMergePolicy(value kueuev1beta1.ProvisioningRequestConfigPodSetMergePolicy) *ProvisioningRequestConfigSpecApplyConfiguration {
+	b.PodSetMergePolicy = &value
+	return b
+}
diff --git a/config/components/crd/bases/kueue.x-k8s.io_provisioningrequestconfigs.yaml b/config/components/crd/bases/kueue.x-k8s.io_provisioningrequestconfigs.yaml
index ca6828e13..5f431a12c 100644
--- a/config/components/crd/bases/kueue.x-k8s.io_provisioningrequestconfigs.yaml
+++ b/config/components/crd/bases/kueue.x-k8s.io_provisioningrequestconfigs.yaml
@@ -68,6 +68,14 @@ spec:
                   require.
                 maxProperties: 100
                 type: object
+              podSetMergePolicy:
+                description: |-
+                  podSetMergePolicy specifies the policy for merging PodSets before being passed
+                  to the cluster autoscaler.
+                enum:
+                - IdenticalPodTemplates
+                - IdenticalWorkloadSchedulingRequirements
+                type: string
               podSetUpdates:
                 description: |-
                   podSetUpdates specifies the update of the workload's PodSetUpdates which
diff --git a/keps/1136-provisioning-request-support/README.md b/keps/1136-provisioning-request-support/README.md
index b94c5491b..a68253daa 100644
--- a/keps/1136-provisioning-request-support/README.md
+++ b/keps/1136-provisioning-request-support/README.md
@@ -139,6 +139,8 @@ Additionally, to enable the previous behavior of keeping the quota, in 0.9.0 we
 will keep the allocated quota and won't be requeued. Instead it will be in the AdmissionCheck phase of admission, and will recreate ProvisioningRequest after backoff time.
 The feature gate is deprecated and will be removed in 0.10 unless we get feedback from users indicating that the old behavior is needed.
 
+The PodSetMergePolicy feature gives the ability to merge similar or identical PodSets into a single PodTemplate within the ProvisioningRequestConfig. This functionality is designed to overcome limitations in certain cloud providers that only allow provisioning one PodTemplate at a time. By merging PodSets, workloads such as PyTorch (which often use similar resources for leader and worker pods) can be efficiently provisioned, reducing overhead and optimizing scaling decisions.
+
 The definition of `ProvisioningRequestConfig` is relatively simple and is based on
 what can be set in `ProvisioningRequest`.
 
@@ -199,6 +201,13 @@ type ProvisioningRequestConfigSpec struct {
 	//
 	// +optional
 	PodSetUpdates *ProvisioningRequestPodSetUpdates `json:"podSetUpdates,omitempty"`
+
+	// podSetMergePolicy specifies the policy for merging PodSets before being passed
+	// to the cluster autoscaler.
+	//
+	// +optional
+	// +kubebuilder:validation:Enum=IdenticalPodTemplates;IdenticalWorkloadSchedulingRequirements
+	PodSetMergePolicy *ProvisioningRequestConfigPodSetMergePolicy `json:"podSetMergePolicy,omitempty"`
 }
 
 type ProvisioningRequestPodSetUpdates struct {
diff --git a/pkg/controller/admissionchecks/provisioning/controller.go b/pkg/controller/admissionchecks/provisioning/controller.go
index 122505857..a3ceb8d4d 100644
--- a/pkg/controller/admissionchecks/provisioning/controller.go
+++ b/pkg/controller/admissionchecks/provisioning/controller.go
@@ -26,6 +26,7 @@ import (
 
 	"github.com/go-logr/logr"
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
 	apimeta "k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -274,17 +275,13 @@ func (c *Controller) syncOwnedProvisionRequest(
 			}
 			passProvReqParams(wl, req)
 
-			expectedPodSets := requiredPodSets(wl.Spec.PodSets, prc.Spec.ManagedResources)
-			psaMap := slices.ToRefMap(wl.Status.Admission.PodSetAssignments, func(p *kueue.PodSetAssignment) kueue.PodSetReference { return p.Name })
-			podSetMap := slices.ToRefMap(wl.Spec.PodSets, func(ps *kueue.PodSet) kueue.PodSetReference { return ps.Name })
-			for _, psName := range expectedPodSets {
-				ps, psFound := podSetMap[psName]
-				psa, psaFound := psaMap[psName]
-				if !psFound || !psaFound {
-					return errInconsistentPodSetAssignments
-				}
+			mergedPodSets, err := mergePodSets(wl, &prc.Spec)
+			if err != nil {
+				return err
+			}
 
-				ptName := getProvisioningRequestPodTemplateName(requestName, psName)
+			for _, mergedPodSet := range mergedPodSets {
+				ptName := getProvisioningRequestPodTemplateName(requestName, mergedPodSet.Name)
 
 				pt := &corev1.PodTemplate{}
 				err := c.client.Get(ctx, types.NamespacedName{Namespace: wl.Namespace, Name: ptName}, pt)
@@ -293,7 +290,7 @@ func (c *Controller) syncOwnedProvisionRequest(
 				}
 				if err != nil {
 					// it's a not found, so create it
-					_, err := c.createPodTemplate(ctx, wl, ptName, ps, psa)
+					_, err := c.createPodTemplate(ctx, wl, ptName, mergedPodSet.PodSet, mergedPodSet.PodSetAssignment)
 					if err != nil {
 						msg := fmt.Sprintf("Error creating PodTemplate %q: %v", ptName, err)
 						return c.handleError(ctx, wl, ac, msg, err)
@@ -304,7 +301,7 @@ func (c *Controller) syncOwnedProvisionRequest(
 					PodTemplateRef: autoscaling.Reference{
 						Name: ptName,
 					},
-					Count: ptr.Deref(psa.Count, ps.Count),
+					Count: mergedPodSet.Count,
 				})
 			}
 
@@ -833,3 +830,84 @@ func limitObjectName(fullName string) string {
 	hashBytes := hex.EncodeToString(h.Sum(nil))
 	return fmt.Sprintf("%s-%s", fullName[:objNameMaxPrefixLength], hashBytes[:objNameHashLength])
 }
+
+type MergedPodSet struct {
+	Name             kueue.PodSetReference
+	PodSet           *kueue.PodSet
+	PodSetAssignment *kueue.PodSetAssignment
+	Count            int32
+}
+
+func mergePodSets(
+	wl *kueue.Workload,
+	prcSpec *kueue.ProvisioningRequestConfigSpec,
+) ([]MergedPodSet, error) {
+	expectedPodSets := requiredPodSets(wl.Spec.PodSets, prcSpec.ManagedResources)
+	psaMap := slices.ToRefMap(wl.Status.Admission.PodSetAssignments, func(p *kueue.PodSetAssignment) kueue.PodSetReference { return p.Name })
+	podSetMap := slices.ToRefMap(wl.Spec.PodSets, func(ps *kueue.PodSet) kueue.PodSetReference { return ps.Name })
+
+	mergePolicy := prcSpec.PodSetMergePolicy
+	mergedPodSets := []MergedPodSet{}
+	for _, psName := range expectedPodSets {
+		ps, psFound := podSetMap[psName]
+		psa, psaFound := psaMap[psName]
+		if !psFound || !psaFound {
+			return nil, errInconsistentPodSetAssignments
+		}
+
+		merged := false
+		if mergePolicy != nil {
+			for i, mps := range mergedPodSets {
+				if merged = canMergePodSets(mps.PodSet, ps, mergePolicy); merged {
+					mergedPodSets[i].Count += ptr.Deref(psa.Count, ps.Count)
+					break
+				}
+			}
+		}
+
+		if !merged {
+			mergedPodSets = append(mergedPodSets, MergedPodSet{
+				Name:             psName,
+				PodSet:           ps,
+				PodSetAssignment: psa,
+				Count:            ptr.Deref(psa.Count, ps.Count),
+			})
+		}
+	}
+
+	return mergedPodSets, nil
+}
+
+func canMergePodSets(ps1, ps2 *kueue.PodSet, mergePolicy *kueue.ProvisioningRequestConfigPodSetMergePolicy) bool {
+	switch *mergePolicy {
+	case kueue.IdenticalPodTemplates:
+		return equality.Semantic.DeepEqual(ps1.Template, ps2.Template)
+	case kueue.IdenticalWorkloadSchedulingRequirements:
+		return arePodSetsSimilar(ps1, ps2)
+	default:
+		return false
+	}
+}
+
+func arePodSetsSimilar(ps1, ps2 *kueue.PodSet) bool {
+	return areContainersEqual(ps1.Template.Spec.Containers, ps2.Template.Spec.Containers) &&
+		areContainersEqual(ps1.Template.Spec.InitContainers, ps2.Template.Spec.InitContainers) &&
+		equality.Semantic.DeepEqual(ps1.Template.Spec.Resources, ps2.Template.Spec.Resources) &&
+		equality.Semantic.DeepEqual(ps1.Template.Spec.NodeSelector, ps2.Template.Spec.NodeSelector) &&
+		equality.Semantic.DeepEqual(ps1.Template.Spec.Tolerations, ps2.Template.Spec.Tolerations) &&
+		equality.Semantic.DeepEqual(ps1.Template.Spec.Affinity, ps2.Template.Spec.Affinity) &&
+		equality.Semantic.DeepEqual(ps1.Template.Spec.ResourceClaims, ps2.Template.Spec.ResourceClaims)
+}
+
+// areContainersEqual compares the resource requests of containers between two lists of PodSet containers.
+func areContainersEqual(ps1Containers []corev1.Container, ps2Containers []corev1.Container) bool {
+	if len(ps1Containers) != len(ps2Containers) {
+		return false
+	}
+	for i := range ps1Containers {
+		if !equality.Semantic.DeepEqual(ps1Containers[i].Resources.Requests, ps2Containers[i].Resources.Requests) {
+			return false
+		}
+	}
+	return true
+}
diff --git a/site/content/en/docs/reference/kueue.v1beta1.md b/site/content/en/docs/reference/kueue.v1beta1.md
index 60fb1993f..3cbb243a9 100644
--- a/site/content/en/docs/reference/kueue.v1beta1.md
+++ b/site/content/en/docs/reference/kueue.v1beta1.md
@@ -2113,6 +2113,18 @@ result in failure during workload admission.</p>
 
 
 
+## `ProvisioningRequestConfigPodSetMergePolicy`     {#kueue-x-k8s-io-v1beta1-ProvisioningRequestConfigPodSetMergePolicy}
+    
+(Alias of `string`)
+
+**Appears in:**
+
+- [ProvisioningRequestConfigSpec](#kueue-x-k8s-io-v1beta1-ProvisioningRequestConfigSpec)
+
+
+
+
+
 ## `ProvisioningRequestConfigSpec`     {#kueue-x-k8s-io-v1beta1-ProvisioningRequestConfigSpec}
     
 
@@ -2177,6 +2189,14 @@ set retryStrategy.backoffLimitCount to 0.</p>
 are used to target the provisioned nodes.</p>
 </td>
 </tr>
+<tr><td><code>podSetMergePolicy</code><br/>
+<a href="#kueue-x-k8s-io-v1beta1-ProvisioningRequestConfigPodSetMergePolicy"><code>ProvisioningRequestConfigPodSetMergePolicy</code></a>
+</td>
+<td>
+   <p>podSetMergePolicy specifies the policy for merging PodSets before being passed
+to the cluster autoscaler.</p>
+</td>
+</tr>
 </tbody>
 </table>
 



Test patch:
diff --git a/pkg/controller/admissionchecks/provisioning/controller_test.go b/pkg/controller/admissionchecks/provisioning/controller_test.go
index e6749169b..f0e6811c3 100644
--- a/pkg/controller/admissionchecks/provisioning/controller_test.go
+++ b/pkg/controller/admissionchecks/provisioning/controller_test.go
@@ -245,6 +245,59 @@ func TestReconcile(t *testing.T) {
 		Parameters(kueue.GroupVersion.Group, ConfigKind, "config1").
 		Obj()
 
+	podSetMergePolicyAssignemnt := []kueue.PodSetAssignment{
+		{
+			Name: "ps1",
+			Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
+				corev1.ResourceCPU: "flv1",
+			},
+			ResourceUsage: map[corev1.ResourceName]resource.Quantity{
+				corev1.ResourceCPU: resource.MustParse("1"),
+			},
+			Count: ptr.To[int32](1),
+		},
+		{
+			Name: "ps2",
+			Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
+				corev1.ResourceCPU: "flv1",
+			},
+			ResourceUsage: map[corev1.ResourceName]resource.Quantity{
+				corev1.ResourceCPU: resource.MustParse("1"),
+			},
+			Count: ptr.To[int32](2),
+		},
+		{
+			Name: "ps3",
+			Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
+				corev1.ResourceCPU: "flv2",
+			},
+			ResourceUsage: map[corev1.ResourceName]resource.Quantity{
+				corev1.ResourceMemory: resource.MustParse("1M"),
+			},
+			Count: ptr.To[int32](2),
+		},
+		{
+			Name: "ps4",
+			Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
+				corev1.ResourceCPU: "flv2",
+			},
+			ResourceUsage: map[corev1.ResourceName]resource.Quantity{
+				corev1.ResourceMemory: resource.MustParse("1M"),
+			},
+			Count: ptr.To[int32](1),
+		},
+		{
+			Name: "ps5",
+			Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
+				corev1.ResourceCPU: "flv2",
+			},
+			ResourceUsage: map[corev1.ResourceName]resource.Quantity{
+				corev1.ResourceMemory: resource.MustParse("1M"),
+			},
+			Count: ptr.To[int32](1),
+		},
+	}
+
 	cases := map[string]struct {
 		interceptorFuncsCreate func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.CreateOption) error
 
@@ -1236,6 +1289,357 @@ func TestReconcile(t *testing.T) {
 					Obj(),
 			},
 		},
+		"with podSetMergePolicy IdenticalPodTemplates": {
+			// podSets 1 and 2 can be merged as they are identical,
+			// podSets 3 and 4 can be merged as they are identical,
+			// podSet 5 however have different priority class even though everything else match with podSets 3 and 4
+			// PodSetMergePolicy IdenticalPodTemplates prevents the ability to merge it
+			workload: utiltesting.MakeWorkload("wl", TestNamespace).
+				AdmissionChecks(kueue.AdmissionCheckState{
+					Name:  "check1",
+					State: kueue.CheckStatePending}).
+				PodSets(
+					*utiltesting.MakePodSet("ps1", 2).
+						Request(corev1.ResourceCPU, "1").
+						Obj(),
+					*utiltesting.MakePodSet("ps2", 2).
+						Request(corev1.ResourceCPU, "1").
+						Obj(),
+					*utiltesting.MakePodSet("ps3", 2).
+						Request(corev1.ResourceMemory, "1M").
+						PriorityClass("pc-100").
+						Obj(),
+					*utiltesting.MakePodSet("ps4", 2).
+						Request(corev1.ResourceMemory, "1M").
+						PriorityClass("pc-100").
+						Obj(),
+					*utiltesting.MakePodSet("ps5", 1).
+						Request(corev1.ResourceMemory, "1M").
+						PriorityClass("pc-200").
+						Obj(),
+				).
+				ReserveQuota(utiltesting.MakeAdmission("q1").PodSets(podSetMergePolicyAssignemnt...).Obj()).
+				AdmissionChecks(kueue.AdmissionCheckState{
+					Name:  "check1",
+					State: kueue.CheckStatePending,
+				}, kueue.AdmissionCheckState{
+					Name:  "not-provisioning",
+					State: kueue.CheckStatePending,
+				}).Obj(),
+			checks:  []kueue.AdmissionCheck{*baseCheck.DeepCopy()},
+			configs: []kueue.ProvisioningRequestConfig{*utiltesting.MakeProvisioningRequestConfig("config1").PodSetMergePolicy(kueue.IdenticalPodTemplates).Obj()},
+			flavors: []kueue.ResourceFlavor{*baseFlavor1.DeepCopy(), *baseFlavor2.DeepCopy()},
+			wantRequests: map[string]*autoscaling.ProvisioningRequest{
+				ProvisioningRequestName("wl", kueue.AdmissionCheckReference(baseCheck.Name), 1): {
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: TestNamespace,
+						Name:      ProvisioningRequestName("wl", kueue.AdmissionCheckReference(baseCheck.Name), 1),
+						Labels: map[string]string{
+							constants.ManagedByKueueLabelKey: constants.ManagedByKueueLabelValue,
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								Name: "wl",
+							},
+						},
+					},
+					Spec: autoscaling.ProvisioningRequestSpec{
+						PodSets: []autoscaling.PodSet{
+							{
+								PodTemplateRef: autoscaling.Reference{
+									Name: "ppt-wl-check1-1-ps1",
+								},
+								Count: 3,
+							},
+							{
+								PodTemplateRef: autoscaling.Reference{
+									Name: "ppt-wl-check1-1-ps3",
+								},
+								Count: 3,
+							},
+							{
+								PodTemplateRef: autoscaling.Reference{
+									Name: "ppt-wl-check1-1-ps5"},
+								Count: 1,
+							},
+						},
+					},
+				},
+			},
+			wantEvents: []utiltesting.EventRecord{
+				{
+					Key:       client.ObjectKeyFromObject(baseWorkload),
+					EventType: corev1.EventTypeNormal,
+					Reason:    "ProvisioningRequestCreated",
+					Message:   `Created ProvisioningRequest: "wl-check1-1"`,
+				},
+			},
+			wantTemplates: map[string]*corev1.PodTemplate{
+				"ppt-wl-check1-1-ps1": utiltesting.MakePodTemplate("ppt-wl-check1-1-ps1", TestNamespace).
+					Label(constants.ManagedByKueueLabelKey, constants.ManagedByKueueLabelValue).
+					Containers(corev1.Container{
+						Name: "c",
+						Resources: corev1.ResourceRequirements{
+							Requests: corev1.ResourceList{
+								corev1.ResourceCPU: resource.MustParse("1"),
+							},
+						},
+					}).
+					NodeSelector("f1l1", "v1").
+					Toleration(corev1.Toleration{
+						Key:      "f1t1k",
+						Value:    "f1t1v",
+						Operator: corev1.TolerationOpEqual,
+						Effect:   corev1.TaintEffectNoSchedule,
+					}).
+					ControllerReference(schema.GroupVersionKind{
+						Group:   "autoscaling.x-k8s.io",
+						Version: "v1beta1",
+						Kind:    "ProvisioningRequest",
+					}, "wl-check1-1", "").
+					Obj(),
+				"ppt-wl-check1-1-ps3": utiltesting.MakePodTemplate("ppt-wl-check1-1-ps3", TestNamespace).
+					Label(constants.ManagedByKueueLabelKey, constants.ManagedByKueueLabelValue).
+					Containers(corev1.Container{
+						Name: "c",
+						Resources: corev1.ResourceRequirements{
+							Requests: corev1.ResourceList{
+								corev1.ResourceMemory: resource.MustParse("1M"),
+							},
+						},
+					}).
+					NodeSelector("f2l1", "v1").
+					PriorityClass("pc-100").
+					ControllerReference(schema.GroupVersionKind{
+						Group:   "autoscaling.x-k8s.io",
+						Version: "v1beta1",
+						Kind:    "ProvisioningRequest",
+					}, "wl-check1-1", "").
+					Obj(),
+				"ppt-wl-check1-1-ps5": utiltesting.MakePodTemplate("ppt-wl-check1-1-ps5", TestNamespace).
+					Label(constants.ManagedByKueueLabelKey, constants.ManagedByKueueLabelValue).
+					Containers(corev1.Container{
+						Name: "c",
+						Resources: corev1.ResourceRequirements{
+							Requests: corev1.ResourceList{
+								corev1.ResourceMemory: resource.MustParse("1M"),
+							},
+						},
+					}).
+					NodeSelector("f2l1", "v1").
+					PriorityClass("pc-200").
+					ControllerReference(schema.GroupVersionKind{
+						Group:   "autoscaling.x-k8s.io",
+						Version: "v1beta1",
+						Kind:    "ProvisioningRequest",
+					}, "wl-check1-1", "").
+					Obj(),
+			},
+		},
+		"with podSetMergePolicy IdenticalWorkloadSchedulingRequirements": {
+			// podSets 1 and 2 can be merged as they are similar, PriorityClass is not taken into account with this PodSetMergePolicy,
+			// podSets 3 and 4 can be merged as they are similar despite different PriorityClass and TopologyRequest,
+			// podSet 5 however have defined an extraAffinity and although everything else match with podSets can't be merged with others
+			workload: utiltesting.MakeWorkload("wl", TestNamespace).
+				AdmissionChecks(kueue.AdmissionCheckState{
+					Name:  "check1",
+					State: kueue.CheckStatePending}).
+				PodSets(
+					*utiltesting.MakePodSet("ps1", 2).
+						Request(corev1.ResourceCPU, "1").
+						PriorityClass("pc-100").
+						Obj(),
+					*utiltesting.MakePodSet("ps2", 2).
+						Request(corev1.ResourceCPU, "1").
+						PriorityClass("pc-200").
+						Obj(),
+					*utiltesting.MakePodSet("ps3", 2).
+						Request(corev1.ResourceMemory, "1M").
+						PriorityClass("pc-100").
+						RequiredTopologyRequest("default1").
+						Obj(),
+					*utiltesting.MakePodSet("ps4", 2).
+						Request(corev1.ResourceMemory, "1M").
+						PriorityClass("pc-200").
+						RequiredTopologyRequest("default2").
+						Obj(),
+					*utiltesting.MakePodSet("ps5", 1).
+						Request(corev1.ResourceMemory, "1M").
+						PriorityClass("pc-300").
+						RequiredDuringSchedulingIgnoredDuringExecution([]corev1.NodeSelectorTerm{
+							{
+								MatchExpressions: []corev1.NodeSelectorRequirement{
+									{
+										Key:      "type",
+										Operator: corev1.NodeSelectorOpIn,
+										Values:   []string{"two"},
+									},
+								},
+							},
+						}).
+						Obj(),
+				).
+				ReserveQuota(utiltesting.MakeAdmission("q1").PodSets(podSetMergePolicyAssignemnt...).Obj()).
+				AdmissionChecks(kueue.AdmissionCheckState{
+					Name:  "check1",
+					State: kueue.CheckStatePending,
+				}, kueue.AdmissionCheckState{
+					Name:  "not-provisioning",
+					State: kueue.CheckStatePending,
+				}).Obj(),
+			checks:  []kueue.AdmissionCheck{*baseCheck.DeepCopy()},
+			configs: []kueue.ProvisioningRequestConfig{*utiltesting.MakeProvisioningRequestConfig("config1").PodSetMergePolicy(kueue.IdenticalWorkloadSchedulingRequirements).Obj()},
+			flavors: []kueue.ResourceFlavor{*baseFlavor1.DeepCopy(), *baseFlavor2.DeepCopy()},
+			wantRequests: map[string]*autoscaling.ProvisioningRequest{
+				ProvisioningRequestName("wl", kueue.AdmissionCheckReference(baseCheck.Name), 1): {
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: TestNamespace,
+						Name:      ProvisioningRequestName("wl", kueue.AdmissionCheckReference(baseCheck.Name), 1),
+						Labels: map[string]string{
+							constants.ManagedByKueueLabelKey: constants.ManagedByKueueLabelValue,
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								Name: "wl",
+							},
+						},
+					},
+					Spec: autoscaling.ProvisioningRequestSpec{
+						PodSets: []autoscaling.PodSet{
+							{
+								PodTemplateRef: autoscaling.Reference{
+									Name: "ppt-wl-check1-1-ps1",
+								},
+								Count: 3,
+							},
+							{
+								PodTemplateRef: autoscaling.Reference{
+									Name: "ppt-wl-check1-1-ps3",
+								},
+								Count: 3,
+							},
+							{
+								PodTemplateRef: autoscaling.Reference{
+									Name: "ppt-wl-check1-1-ps5"},
+								Count: 1,
+							},
+						},
+					},
+				},
+			},
+			wantEvents: []utiltesting.EventRecord{
+				{
+					Key:       client.ObjectKeyFromObject(baseWorkload),
+					EventType: corev1.EventTypeNormal,
+					Reason:    "ProvisioningRequestCreated",
+					Message:   `Created ProvisioningRequest: "wl-check1-1"`,
+				},
+			},
+			wantTemplates: map[string]*corev1.PodTemplate{
+				"ppt-wl-check1-1-ps1": utiltesting.MakePodTemplate("ppt-wl-check1-1-ps1", TestNamespace).
+					Label(constants.ManagedByKueueLabelKey, constants.ManagedByKueueLabelValue).
+					Containers(corev1.Container{
+						Name: "c",
+						Resources: corev1.ResourceRequirements{
+							Requests: corev1.ResourceList{
+								corev1.ResourceCPU: resource.MustParse("1"),
+							},
+						},
+					}).
+					NodeSelector("f1l1", "v1").
+					PriorityClass("pc-100").
+					Toleration(corev1.Toleration{
+						Key:      "f1t1k",
+						Value:    "f1t1v",
+						Operator: corev1.TolerationOpEqual,
+						Effect:   corev1.TaintEffectNoSchedule,
+					}).
+					ControllerReference(schema.GroupVersionKind{
+						Group:   "autoscaling.x-k8s.io",
+						Version: "v1beta1",
+						Kind:    "ProvisioningRequest",
+					}, "wl-check1-1", "").
+					Obj(),
+				"ppt-wl-check1-1-ps3": utiltesting.MakePodTemplate("ppt-wl-check1-1-ps3", TestNamespace).
+					Label(constants.ManagedByKueueLabelKey, constants.ManagedByKueueLabelValue).
+					Containers(corev1.Container{
+						Name: "c",
+						Resources: corev1.ResourceRequirements{
+							Requests: corev1.ResourceList{
+								corev1.ResourceMemory: resource.MustParse("1M"),
+							},
+						},
+					}).
+					NodeSelector("f2l1", "v1").
+					PriorityClass("pc-100").
+					ControllerReference(schema.GroupVersionKind{
+						Group:   "autoscaling.x-k8s.io",
+						Version: "v1beta1",
+						Kind:    "ProvisioningRequest",
+					}, "wl-check1-1", "").
+					Obj(),
+				"ppt-wl-check1-1-ps5": utiltesting.MakePodTemplate("ppt-wl-check1-1-ps5", TestNamespace).
+					Label(constants.ManagedByKueueLabelKey, constants.ManagedByKueueLabelValue).
+					Containers(corev1.Container{
+						Name: "c",
+						Resources: corev1.ResourceRequirements{
+							Requests: corev1.ResourceList{
+								corev1.ResourceMemory: resource.MustParse("1M"),
+							},
+						},
+					}).
+					NodeSelector("f2l1", "v1").
+					PriorityClass("pc-300").
+					RequiredDuringSchedulingIgnoredDuringExecution([]corev1.NodeSelectorTerm{
+						{
+							MatchExpressions: []corev1.NodeSelectorRequirement{
+								{
+									Key:      "type",
+									Operator: corev1.NodeSelectorOpIn,
+									Values:   []string{"two"},
+								},
+							},
+						},
+					}).
+					ControllerReference(schema.GroupVersionKind{
+						Group:   "autoscaling.x-k8s.io",
+						Version: "v1beta1",
+						Kind:    "ProvisioningRequest",
+					}, "wl-check1-1", "").
+					Obj(),
+			},
+		},
+		"with podSetMergePolicy but no PodSetAssignments": {
+			// podSets 1 and 2 can be merged as they are similar, PriorityClass is not taken into account with this PodSetMergePolicy,
+			// podSets 3 and 4 can be merged as they are similar despite different PriorityClass and TopologyRequest,
+			// podSet 5 however have defined an extraAffinity and although everything else match with podSets can't be merged with others
+			workload: utiltesting.MakeWorkload("wl", TestNamespace).
+				AdmissionChecks(kueue.AdmissionCheckState{
+					Name:  "check1",
+					State: kueue.CheckStatePending}).
+				PodSets(
+					*utiltesting.MakePodSet("ps11", 2).
+						Request(corev1.ResourceCPU, "1").
+						PriorityClass("pc-100").
+						Obj(),
+					*utiltesting.MakePodSet("ps22", 2).
+						Request(corev1.ResourceCPU, "1").
+						PriorityClass("pc-200").
+						Obj(),
+				).
+				ReserveQuota(utiltesting.MakeAdmission("q1").PodSets(podSetMergePolicyAssignemnt...).Obj()).
+				AdmissionChecks(kueue.AdmissionCheckState{
+					Name:  "check1",
+					State: kueue.CheckStatePending,
+				}, kueue.AdmissionCheckState{
+					Name:  "not-provisioning",
+					State: kueue.CheckStatePending,
+				}).Obj(),
+			checks:             []kueue.AdmissionCheck{*baseCheck.DeepCopy()},
+			configs:            []kueue.ProvisioningRequestConfig{*utiltesting.MakeProvisioningRequestConfig("config1").PodSetMergePolicy(kueue.IdenticalWorkloadSchedulingRequirements).Obj()},
+			flavors:            []kueue.ResourceFlavor{*baseFlavor1.DeepCopy(), *baseFlavor2.DeepCopy()},
+			wantReconcileError: errInconsistentPodSetAssignments,
+		},
 	}
 
 	for name, tc := range cases {
diff --git a/pkg/util/testing/wrappers.go b/pkg/util/testing/wrappers.go
index 11382bfab..e3ea15696 100644
--- a/pkg/util/testing/wrappers.go
+++ b/pkg/util/testing/wrappers.go
@@ -420,6 +420,11 @@ func (p *PodSetWrapper) RuntimeClass(name string) *PodSetWrapper {
 	return p
 }
 
+func (p *PodSetWrapper) RestartPolicy(policy corev1.RestartPolicy) *PodSetWrapper {
+	p.Template.Spec.RestartPolicy = policy
+	return p
+}
+
 func (p *PodSetWrapper) RequiredTopologyRequest(level string) *PodSetWrapper {
 	if p.TopologyRequest == nil {
 		p.TopologyRequest = &kueue.PodSetTopologyRequest{}
@@ -1507,6 +1512,11 @@ func (prc *ProvisioningRequestConfigWrapper) RetryLimit(backoffLimitCount int32)
 	return prc
 }
 
+func (prc *ProvisioningRequestConfigWrapper) PodSetMergePolicy(mode kueue.ProvisioningRequestConfigPodSetMergePolicy) *ProvisioningRequestConfigWrapper {
+	prc.Spec.PodSetMergePolicy = &mode
+	return prc
+}
+
 func (prc *ProvisioningRequestConfigWrapper) Clone() *ProvisioningRequestConfigWrapper {
 	return &ProvisioningRequestConfigWrapper{ProvisioningRequestConfig: *prc.DeepCopy()}
 }
@@ -1564,6 +1574,28 @@ func (w *PodTemplateWrapper) Toleration(toleration corev1.Toleration) *PodTempla
 	return w
 }
 
+func (p *PodTemplateWrapper) PriorityClass(pc string) *PodTemplateWrapper {
+	p.Template.Spec.PriorityClassName = pc
+	return p
+}
+
+func (p *PodTemplateWrapper) RequiredDuringSchedulingIgnoredDuringExecution(nodeSelectorTerms []corev1.NodeSelectorTerm) *PodTemplateWrapper {
+	if p.Template.Spec.Affinity == nil {
+		p.Template.Spec.Affinity = &corev1.Affinity{}
+	}
+	if p.Template.Spec.Affinity.NodeAffinity == nil {
+		p.Template.Spec.Affinity.NodeAffinity = &corev1.NodeAffinity{}
+	}
+	if p.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
+		p.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = &corev1.NodeSelector{}
+	}
+	p.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = append(
+		p.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms,
+		nodeSelectorTerms...,
+	)
+	return p
+}
+
 func (w *PodTemplateWrapper) ControllerReference(gvk schema.GroupVersionKind, name, uid string) *PodTemplateWrapper {
 	AppendOwnerReference(&w.PodTemplate, gvk, name, uid, ptr.To(true), ptr.To(true))
 	return w
diff --git a/test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go b/test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go
index 1731b8660..31972fec4 100644
--- a/test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go
+++ b/test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go
@@ -1256,4 +1256,160 @@ var _ = ginkgo.Describe("Provisioning", ginkgo.Ordered, ginkgo.ContinueOnFailure
 			})
 		})
 	})
+
+	ginkgo.When("A workload is using a provision request with enabled PodSetMergePolicy", func() {
+		var (
+			ns             *corev1.Namespace
+			wlKey          types.NamespacedName
+			provReqKey     types.NamespacedName
+			ac             *kueue.AdmissionCheck
+			prc            *kueue.ProvisioningRequestConfig
+			rf             *kueue.ResourceFlavor
+			cq             *kueue.ClusterQueue
+			lq             *kueue.LocalQueue
+			admission      *kueue.Admission
+			createdRequest autoscaling.ProvisioningRequest
+			updatedWl      kueue.Workload
+		)
+
+		ginkgo.JustBeforeEach(func() {
+			ns = util.CreateNamespaceFromPrefixWithLog(ctx, k8sClient, "provisioning-")
+
+			prc = baseConfig.Clone().
+				RetryLimit(0).
+				PodSetMergePolicy(kueue.IdenticalWorkloadSchedulingRequirements).
+				Obj()
+			util.MustCreate(ctx, k8sClient, prc)
+
+			ac = testing.MakeAdmissionCheck("ac-prov").
+				ControllerName(kueue.ProvisioningRequestControllerName).
+				Parameters(kueue.GroupVersion.Group, "ProvisioningRequestConfig", "prov-config").
+				Obj()
+			util.MustCreate(ctx, k8sClient, ac)
+
+			rf = testing.MakeResourceFlavor(flavorOnDemand).NodeLabel("ns1", "ns1v").Obj()
+			util.MustCreate(ctx, k8sClient, rf)
+
+			cq = testing.MakeClusterQueue("cluster-queue").
+				ResourceGroup(*testing.MakeFlavorQuotas(flavorOnDemand).
+					Resource(resourceGPU, "5", "5").Obj()).
+				Cohort("cohort").
+				AdmissionChecks(kueue.AdmissionCheckReference(ac.Name)).
+				Obj()
+			util.MustCreate(ctx, k8sClient, cq)
+			util.ExpectClusterQueuesToBeActive(ctx, k8sClient, cq)
+
+			lq = testing.MakeLocalQueue("queue", ns.Name).ClusterQueue(cq.Name).Obj()
+			util.MustCreate(ctx, k8sClient, lq)
+			gomega.Eventually(func(g gomega.Gomega) {
+				g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(lq), lq)).Should(gomega.Succeed())
+				g.Expect(lq.Status.Conditions).Should(testing.HaveConditionStatusTrue(kueue.LocalQueueActive))
+			}, util.Timeout, util.Interval).Should(gomega.Succeed())
+
+			wl := testing.MakeWorkload("wl", ns.Name).
+				Queue(kueue.LocalQueueName(lq.Name)).
+				PodSets(
+					*testing.MakePodSet("master", 1).
+						Request(corev1.ResourceCPU, "1").
+						Request(corev1.ResourceMemory, "2Gi").
+						Image("image").
+						Labels(map[string]string{"role": "master"}).
+						Obj(),
+					*testing.MakePodSet("worker", 4).
+						Request(corev1.ResourceCPU, "1").
+						Request(corev1.ResourceMemory, "2Gi").
+						Image("image").
+						Labels(map[string]string{"role": "worker"}).
+						Obj(),
+				).
+				Obj()
+			util.MustCreate(ctx, k8sClient, wl)
+
+			wlKey = client.ObjectKeyFromObject(wl)
+			provReqKey = types.NamespacedName{
+				Namespace: wlKey.Namespace,
+				Name:      provisioning.ProvisioningRequestName(wlKey.Name, kueue.AdmissionCheckReference(ac.Name), 1),
+			}
+
+			admission = testing.MakeAdmission(cq.Name).
+				PodSets(
+					kueue.PodSetAssignment{
+						Name: "master",
+						Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
+							corev1.ResourceCPU: kueue.ResourceFlavorReference(rf.Name),
+						},
+						ResourceUsage: map[corev1.ResourceName]resource.Quantity{
+							corev1.ResourceCPU:    resource.MustParse("1"),
+							corev1.ResourceMemory: resource.MustParse("2Gi"),
+						},
+						Count: ptr.To[int32](1),
+					},
+					kueue.PodSetAssignment{
+						Name: "worker",
+						Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
+							corev1.ResourceCPU: kueue.ResourceFlavorReference(rf.Name),
+						},
+						ResourceUsage: map[corev1.ResourceName]resource.Quantity{
+							corev1.ResourceCPU:    resource.MustParse("1"),
+							corev1.ResourceMemory: resource.MustParse("2Gi"),
+						},
+						Count: ptr.To[int32](2),
+					},
+				).
+				Obj()
+		})
+
+		ginkgo.AfterEach(func() {
+			gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, cq, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, rf, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, ac, true)
+			util.ExpectObjectToBeDeleted(ctx, k8sClient, prc, true)
+		})
+
+		ginkgo.It("Should merge similar PodSets into one PodTemplate, PodSetMergePolicy is IdenticalWorkloadSchedulingRequirements", func() {
+			ginkgo.By("Setting the quota reservation to the workload", func() {
+				gomega.Eventually(func(g gomega.Gomega) {
+					g.Expect(k8sClient.Get(ctx, wlKey, &updatedWl)).Should(gomega.Succeed())
+					g.Expect(util.SetQuotaReservation(ctx, k8sClient, &updatedWl, admission)).To(gomega.Succeed())
+				}, util.Timeout, util.Interval).Should(gomega.Succeed())
+			})
+
+			ginkgo.By("Checking that the provision request is created", func() {
+				gomega.Eventually(func(g gomega.Gomega) {
+					g.Expect(k8sClient.Get(ctx, provReqKey, &createdRequest)).Should(gomega.Succeed())
+				}, util.Timeout, util.Interval).Should(gomega.Succeed())
+			})
+
+			ignoreContainersDefaults := cmpopts.IgnoreFields(corev1.Container{}, "TerminationMessagePath", "TerminationMessagePolicy", "ImagePullPolicy")
+			ginkgo.By("Checking that the provision requests content", func() {
+				gomega.Expect(createdRequest.Spec.ProvisioningClassName).To(gomega.Equal("provisioning-class"))
+				gomega.Expect(createdRequest.Spec.PodSets).To(gomega.HaveLen(1))
+				gomega.Expect(createdRequest.ObjectMeta.GetLabels()).To(gomega.BeComparableTo(map[string]string{constants.ManagedByKueueLabelKey: constants.ManagedByKueueLabelValue}))
+
+				mergedPodSet := createdRequest.Spec.PodSets[0]
+				gomega.Expect(mergedPodSet.Count).To(gomega.Equal(int32(3)))
+				gomega.Expect(mergedPodSet.PodTemplateRef.Name).NotTo(gomega.BeEmpty())
+
+				mergedTemplate := &corev1.PodTemplate{}
+				templateKey := types.NamespacedName{
+					Namespace: createdRequest.Namespace,
+					Name:      mergedPodSet.PodTemplateRef.Name,
+				}
+				gomega.Eventually(func(g gomega.Gomega) {
+					g.Expect(k8sClient.Get(ctx, templateKey, mergedTemplate)).Should(gomega.Succeed())
+				}, util.Timeout, util.Interval).Should(gomega.Succeed())
+				gomega.Expect(mergedTemplate.Template.Spec.Containers).To(gomega.BeComparableTo(updatedWl.Spec.PodSets[0].Template.Spec.Containers, ignoreContainersDefaults))
+				gomega.Expect(mergedTemplate.Template.Spec.NodeSelector).To(gomega.BeComparableTo(map[string]string{"ns1": "ns1v"}))
+				gomega.Expect(mergedTemplate.ObjectMeta.GetLabels()).To(gomega.BeComparableTo(map[string]string{constants.ManagedByKueueLabelKey: constants.ManagedByKueueLabelValue}))
+			})
+
+			ginkgo.By("Removing the quota reservation from the workload", func() {
+				gomega.Eventually(func(g gomega.Gomega) {
+					g.Expect(k8sClient.Get(ctx, wlKey, &updatedWl)).Should(gomega.Succeed())
+					g.Expect(util.SetQuotaReservation(ctx, k8sClient, &updatedWl, nil)).To(gomega.Succeed())
+				}, util.Timeout, util.Interval).Should(gomega.Succeed())
+			})
+		})
+	})
 })