Skip to content

Add Scheduler Backend framework#293

Open
kangclzjc wants to merge 8 commits intoai-dynamo:mainfrom
kangclzjc:scheduler_backend
Open

Add Scheduler Backend framework#293
kangclzjc wants to merge 8 commits intoai-dynamo:mainfrom
kangclzjc:scheduler_backend

Conversation

@kangclzjc
Copy link
Contributor

@kangclzjc kangclzjc commented Dec 22, 2025

What type of PR is this?

In order to support different scheduler as backends we modify Grove and import scheduler backend interface

What this PR does / why we need it:

In the current PodGang component's sync flow we do the following:

  • Get the list of PodGangs that are expected to be created for the PCS.
  • Check which ones are pending to be created. For each pending PodGang we do the following:
    • Check if all pods have been created for the PodGang.
    • Check if all pods have required PodGang label which adds back reference to the PodGang.
  • If all the above checks are satisfied then it will go ahead and create the PodGang resource.
    So you can see PodGang will be created after pods. However, there is a problem with upcoming Workload API support and kube-scheduler backend.

We don't want break current PodGang working flow. We import this scheduler backend framework to leave the Workload management work to scheduler backend in Grove. For other scheduler, scheduler backend in Grove may manage different CR based on PodGang(Just like KAI, it will create PodGroups. In the future, we will move this management from KAI scheduler to Grove scheduler backend).

To create a Workload object, you will need to create PodGang resource. The PodGang resource cannot be created before the Pods have been created and have a back reference to the PodGang. The issue is that only after the Workload object is created will the kube-scheduler choose to run scoring/filtering plugins to reserve node capacity to schedule this workload PodGroups. The Pods need to have a reference to the Workload object in their spec.

So to accommodate Workload API the flow needs to be changed as below in the PodGang component:

  • Create PodGang with PodGroups(having empty PodReferences as none will exist at this point) and Initialized condition set to False.
  • Creation of PodGang will trigger the creation of the Workload object in the schedulerbackend reconciler which will use the kube scheduler backend.

    This is out of scope of this PR and should be included in the next PR which specifically handles the Workload APi and kube-scheduler.

  • Once all Pod references are updated then set it to true
  • Pods should not lift their scheduling gate till PodGang has Initialized condition set to True. - done in the PCLQ reconciler.

Which issue(s) this PR fixes:

Fixes #275

Special notes for your reviewer:

Does this PR introduce a API change?

Yes. We will introduce a new API SchedulerBackend

type SchedulerBackend interface {
	// Name is a unique name of the scheduler backend.
	Name() string

	// Init provides a hook to initialize/setup one-time scheduler resources,
	// called at the startup of grove operator.
	Init() error

	// SyncPodGang synchronizes (creates/updates) scheduler specific resources for a PodGang
	// reacting to a creation or update of a PodGang resource.
	SyncPodGang(ctx context.Context, podGang *groveschedulerv1alpha1.PodGang) error

	// OnPodGangDelete cleans up scheduler specific resources for the given PodGang.
	OnPodGangDelete(ctx context.Context, podGang *groveschedulerv1alpha1.PodGang) error

	// PreparePod adds scheduler backend specific configuration to the given Pod object
	// prior to its creation. This includes setting schedulerName, scheduling gates,
	// annotations, etc.
	PreparePod(pod *corev1.Pod)
}

Additional documentation e.g., enhancement proposals, usage docs, etc.:


@gflarity
Copy link
Contributor

gflarity commented Jan 2, 2026

Not sure I quite understand the goals of this, it's already possible to support different schedulers via the pod specs? (though Kai is the only gang scheduler currently working). I'd suggest kicking off work like this off with a github issue with plenty of detail and a discord discussion as well.

@kangclzjc kangclzjc changed the title Add Scheduler Backend with KAI as default Add Scheduler Backend framework Jan 5, 2026
@kangclzjc
Copy link
Contributor Author

kangclzjc commented Jan 5, 2026

Not sure I quite understand the goals of this, it's already possible to support different schedulers via the pod specs? (though Kai is the only gang scheduler currently working). I'd suggest kicking off work like this off with a github issue with plenty of detail and a discord discussion as well.

Sure, Let me create a new issue to introduce this. I can introduce some background here. This is a real request from one of our customer. We have some schedulers which want to integrate Grove. It would be great to have a unify scheduler backend. In that way, we can support other schedulers easily. Since we need to support multiple scheduler as backend especially we need to support k8s 1.34 workload API. Once we have this backend framework we can easily add new scheduler support like default-kube scheduler, Koordinator. In this PR I will only involve scheduler backend framework. For KAI scheduler backend, I won't change the currently workflow that means KAI will still handle podgang and create podgroups/pods.

@kangclzjc kangclzjc force-pushed the scheduler_backend branch 2 times, most recently from 418038f to 206f953 Compare January 8, 2026 01:17
@kangclzjc kangclzjc marked this pull request as ready for review January 8, 2026 03:33
Copy link
Contributor

@Ronkahn21 Ronkahn21 left a comment

Choose a reason for hiding this comment

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

Overall looks great! A few architectural points to consider:

  • Controller Responsibility: I don’t think the pcs-controller should be updating the PodGang status. Ideally, it should only handle the creation, leaving the podGang-controller to manage its own status.

  • Scaling & Performance: We should discuss the PodGang pod reference fields. Adding this to the pcs-controller increases its complexity. For better scalability, it might be better to let the PodGroup own the pod status before we move toward creating the backend API.

Since the API changes are currently out of scope, we can sync on this later. Amazing job overall, thanks!

Comment on lines 307 to 330
// Also verify that all PodGroups have enough podReferences to meet minReplicas
for _, pg := range podGang.Spec.PodGroups {
if int32(len(pg.PodReferences)) < pg.MinReplicas {
return false
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

can you explain why it is a requirement for filtering the events?,
I am afraid will be missing event handleling

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can you explain why it is a requirement for filtering the events?, I am afraid will be missing event handleling

For reconcilers that are registered to react to PodGang create/update events we should now consider looking at the PodGangInitialized condition with its value set to True to allow enqueue of events for the reconciler. I add it because I only want to handle event when we have a stable(enough replicas) status. If PodGangInitialized is false then it means PodGang isn't been completely create yet(without PodReferece), in this case we don't need to handle event I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, based on GREP, we don't need compare replicas since PodGangInitialized = true means we have enough pod references in podgang

Comment on lines +522 to +606
sort.Slice(podReferences, func(i, j int) bool {
return podReferences[i].Name < podReferences[j].Name
})
Copy link
Contributor

Choose a reason for hiding this comment

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

we might need to change the api field to ignore order, to reduce case where sorting big pods list each time

Copy link
Contributor Author

@kangclzjc kangclzjc Jan 9, 2026

Choose a reason for hiding this comment

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

we might need to change the api field to ignore order, to reduce case where sorting big pods list each time

That's a good idea since we have customer which have over 500 pods. But in this case, we won't have the order of the pods. I am not sure is it acceptable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And if you do not sort it then there will be unnecessary updates to the PodGangs.


const (
// BackendName is the name of the KAI backend
BackendName = "kai"
Copy link
Contributor

Choose a reason for hiding this comment

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

KAI

Suggested change
BackendName = "kai"
BackendName = "KAI-Scheduler"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I know kai is better enough, only 1 thing, that in pod Spec we also use kai-scheduler/default-scheduler. So I unify all this to kai-scheduler/default-scheduler, even in values.yaml.

@kangclzjc
Copy link
Contributor Author

Overall looks great! A few architectural points to consider:

  • Controller Responsibility: I don’t think the pcs-controller should be updating the PodGang status. Ideally, it should only handle the creation, leaving the podGang-controller to manage its own status.
  • Scaling & Performance: We should discuss the PodGang pod reference fields. Adding this to the pcs-controller increases its complexity. For better scalability, it might be better to let the PodGroup own the pod status before we move toward creating the backend API.

Since the API changes are currently out of scope, we can sync on this later. Amazing job overall, thanks!

  1. We don't have PodGang-controller currently, so do you mean add a new podGang-controller?
  2. Actually if we use default kube-scheduler, we won't have PodGroup, so we'd better use pcs-controller to fill the pod reference fields.

@unmarshall unmarshall added kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/enhancement Categorizes issue or PR as related to a new feature, enhancement or improvement component/scheduler Issue/PR is for scheduler module component/operator Issue/PR is for grove operator module size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jan 17, 2026
@kangclzjc kangclzjc force-pushed the scheduler_backend branch 3 times, most recently from aa4ca3b to b0b609c Compare January 29, 2026 03:12
Copy link
Collaborator

@unmarshall unmarshall left a comment

Choose a reason for hiding this comment

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

1/n reviews

@unmarshall
Copy link
Collaborator

@kangclzjc please rebase your PR so that it becomes easier to review this PR.

Copy link
Collaborator

@unmarshall unmarshall left a comment

Choose a reason for hiding this comment

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

2/n review comments

return err
}
if err := registerWebhooksWithMgr(mgr, operatorCfg.Authorizer, operatorCfg.TopologyAwareScheduling, operatorCfg.Network); err != nil {
if err := registerWebhooksWithMgr(mgr, operatorCfg.Authorizer, operatorCfg.TopologyAwareScheduling, operatorCfg.Network, string(operatorCfg.SchedulerName)); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

By your own arguments in comment can you replace arguments to registerWebhooksWithMgr with just 2 arguments namely mgr and operatorCfg?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, and since in this function, there are still other functions(many NewHandler) call with multiple parameters, do u think we should modify them together like below ?

pcsValidatingWebhook := pcsvalidation.NewHandler(mgr, operatorCfg.TopologyAwareScheduling, operatorCfg.Network, string(operatorCfg.SchedulerName))

@kangclzjc kangclzjc force-pushed the scheduler_backend branch 2 times, most recently from 5855c30 to 847e4dc Compare February 1, 2026 05:57
@kangclzjc kangclzjc force-pushed the scheduler_backend branch 2 times, most recently from 75dfe3b to bbe66b0 Compare February 2, 2026 04:48
Signed-off-by: kangclzjc <kangz@nvidia.com>
Signed-off-by: kangclzjc <kangz@nvidia.com>
Signed-off-by: kangclzjc <kangz@nvidia.com>
Signed-off-by: kangclzjc <kangz@nvidia.com>
Signed-off-by: kangclzjc <kangz@nvidia.com>
Signed-off-by: kangclzjc <kangz@nvidia.com>
@copy-pr-bot
Copy link

copy-pr-bot bot commented Feb 3, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Signed-off-by: kangclzjc <kangz@nvidia.com>

// PreparePod adds KAI scheduler-specific configuration to the Pod
// This includes: labels, annotations, etc.
func (b *Backend) PreparePod(_ *corev1.Pod) {
Copy link

Choose a reason for hiding this comment

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

I think we should add schedulerName: kai-scheduler to the pod spec here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For all schedulers backend, it all need to add schedulerName, so I unify this in buildResource function. Because I think it's should be common for every backends and every pods.

	pod.Spec.SchedulerName = schedulerbackend.Get().Name()

	// Use backend to prepare Pod spec based on scheduler requirements
	// This adds labels, annotations, etc.
	if err = schedulerbackend.PreparePod(pod); err != nil {
		return groveerr.WrapError(err,
			errCodeBuildPodResource,
			component.OperationSync,
			"failed to prepare pod spec with scheduler backend",
		)
	}


// NewHandler creates a new handler for PodCliqueSet Webhook.
func NewHandler(mgr manager.Manager, tasConfig configv1alpha1.TopologyAwareSchedulingConfiguration, networkConfig configv1alpha1.NetworkAcceleration) *Handler {
func NewHandler(mgr manager.Manager, tasConfig configv1alpha1.TopologyAwareSchedulingConfiguration, networkConfig configv1alpha1.NetworkAcceleration, schedulerName string) *Handler {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@unmarshall do u think we should also group them all as only two parameters since they may all come from one parent config

concurrentSyncs: 3
podCliqueScalingGroup:
concurrentSyncs: 2
schedulerName: kai-scheduler
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ideally we should not be creating multiple configuration test data files. Instead just creating one is sufficient. Use go templates to create a test-config-template.yaml and in tests replace the template parameters with different values. But maybe we do this as a separate PR since you have not really introduced any new testdata file as part of this PR but only added schedulerName to an existing one.

operatorConfig.SchedulerName,
); err != nil {
logger.Error(err, "failed to initialize scheduler backend")
handleErrorAndExit(err, cli.ExitErrInitializeManager)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you introduce a new code ErrInitializeSchedulerBackend and use it here?

func RegisterControllers(mgr ctrl.Manager, controllerConfig configv1alpha1.ControllerConfiguration, topologyAwareSchedulingConfig configv1alpha1.TopologyAwareSchedulingConfiguration, networkConfig configv1alpha1.NetworkAcceleration) error {
func RegisterControllers(mgr ctrl.Manager, config configv1alpha1.OperatorConfiguration) error {
controllerConfig := config.Controllers
topologyAwareSchedulingConfig := config.TopologyAwareScheduling
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a need to define a new variable topologyAwareSchedulingConfig?
You could have directly used it:

	pcsReconciler := podcliqueset.NewReconciler(mgr, controllerConfig.PodCliqueSet, config.TopologyAwareScheduling, networkConfig)

You also do not need to define networkConfig. It is not bringing any additional value.

if err != nil {
return fmt.Errorf("failed to create backend reconciler: %w", err)
}
if err := backendReconciler.RegisterWithManager(mgr); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

no need to short assign err since err variable is brought into function scope via backendReconciler, err := backendcontroller.NewReconciler(mgr)

},
PodCliqueScalingGroup: configv1alpha1.PodCliqueScalingGroupControllerConfiguration{
ConcurrentSyncs: ptr.To(1),
operatorConfig := configv1alpha1.OperatorConfiguration{
Copy link
Collaborator

Choose a reason for hiding this comment

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

there seems to be no difference between successful registration and registration with higher concurrency - what are we effectively testing with these 2 variations? Not clear.

// limitations under the License.
// */

package kai
Copy link
Collaborator

Choose a reason for hiding this comment

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

Only introduce the test when you have the implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it


package kube

import (
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just having KAI place holder implementation is enough. You can perhaps remove kube package altogether. Both of these have empty implementation of methods so there is no point to have more than one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I remove kube package here then once we merge this PR, the main branch can't support default kube scheduler unless we add an implementation in another PR

// Initialize creates the global backend instance based on schedulerName
// This should be called once during operator startup
// Supported scheduler names: "kai-scheduler", "default-scheduler"
func Initialize(client client.Client, scheme *runtime.Scheme, eventRecorder record.EventRecorder, schedulerName configv1alpha1.SchedulerName) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am a bit confused about this function and this file.
We have a SchedulerBackend.Init method which was supposed to do this, right? The question is why is this here then?


// Validate that the scheduler name matches the one Grove was configured with
if len(uniqueSchedulerNames) > 0 && v.schedulerName != "" {
userSchedulerName := uniqueSchedulerNames[0]
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you rename userSchedulerName to pcsSchedulerName

PodGangConditionTypeReady PodGangConditionType = "Ready"
// PodGangConditionTypeInitialized indicates that all Pods have been created and PodGang has been populated with pod references.
// This condition is set to True after all pods are created, signaling that scheduling gates can be removed.
PodGangConditionTypeInitialized PodGangConditionType = "Initialized"
Copy link
Collaborator

Choose a reason for hiding this comment

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

We defined conditions in api/common/constants.go and there we have defined constant for both condition and condition reasons. Maybe we do similar changes for PodGang as well?

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

Labels

component/operator Issue/PR is for grove operator module component/scheduler Issue/PR is for scheduler module kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/enhancement Categorizes issue or PR as related to a new feature, enhancement or improvement size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Native Support for Kubernetes Workload API to Enable Gang Scheduling

5 participants