Skip to content

Enhance network-topology-aware plugin to provide hypernode-level binpacking for normal pods - #4889

Merged
volcano-sh-bot merged 2 commits into
volcano-sh:masterfrom
Tau721:feat_hn-level_normal_pods_binpacking
Jan 20, 2026
Merged

Enhance network-topology-aware plugin to provide hypernode-level binpacking for normal pods#4889
volcano-sh-bot merged 2 commits into
volcano-sh:masterfrom
Tau721:feat_hn-level_normal_pods_binpacking

Conversation

@Tau721

@Tau721 Tau721 commented Dec 29, 2025

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind feature

What this PR does / why we need it:

Currently, the network-topology-aware plugin provides hypernode-level binpacking capability for jobs configured with a network topology. However, it fails to implement this feature for normal pods, i.e., the tasks of jobs that are not configured with any network topology. As a result, these tasks may be scheduled to all hypernodes in the cluster, leading to severe resource fragmentation and waste among hypernodes.

To this end, this PR tries to enhance the network-topology-aware plugin to provide hypernode-level binpacking for normal pods.

Which issue(s) this PR fixes:

Fixes #4869

Does this PR introduce a user-facing change?

The following two plugin arguments are added:
1. hypernode.binpack.normal-pod.enable: type of bool, used to control whether or not to enable hypernode-level binpacking for pods without network topology.
2. hypernode.binpack.normal-pod.fading: type of float64, used to control the tier weight of each hypernode, i.e., hypernode tier weight = math.Pow(fading, (tier-1)).

@volcano-sh-bot volcano-sh-bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. kind/feature Categorizes issue or PR as related to a new feature. labels Dec 29, 2025
@volcano-sh-bot

Copy link
Copy Markdown
Contributor

Welcome @Tau721! It looks like this is your first PR to volcano-sh/volcano 🎉

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @Tau721, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the network-topology-aware scheduler plugin by extending its hypernode-level binpacking capabilities. Previously, this feature was limited to jobs with explicit network topology configurations, leading to potential resource fragmentation for other pods. The changes introduce binpacking for these 'normal pods,' ensuring more efficient and consolidated resource utilization across hypernodes. This enhancement aims to reduce resource waste and optimize scheduling decisions for a broader range of workloads.

Highlights

  • Hypernode-level Binpacking for Normal Pods: The network-topology-aware plugin is enhanced to provide hypernode-level binpacking for pods that are not configured with any network topology, addressing resource fragmentation.
  • New Plugin Arguments: Two new plugin arguments are introduced: hypernode.binpack.normal-pod.enable (boolean) to control the feature, and hypernode.binpack.normal-pod.fading (float64) to adjust hypernode tier weights.
  • Resource Cache and Event Handling: A hyperNodeResourceCache is added to store resource status, and an EventHandler is implemented to update this cache on pod allocation and deallocation events, optimizing resource calculation.
  • Refactored Scoring Logic: The scoring functions (HyperNodeOrderFn, batchNodeOrderFn) have been refactored to separate logic for network-aware and normal pods, and a normalizeFinalScore function was introduced for consistent score scaling.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@volcano-sh-bot volcano-sh-bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. do-not-merge/contains-merge-commits labels Dec 29, 2025

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the network-topology-aware plugin to provide hypernode-level bin-packing for normal pods, which is a valuable feature for reducing resource fragmentation. The changes are well-structured, introducing new configuration options, refactoring the scoring logic for better clarity, and adding a resource cache to improve performance. The accompanying tests are comprehensive. I have a few suggestions, including a correction for a potential logic flaw in the new scoring mechanism that might impact scheduling behavior, a performance optimization for the resource cache updates, and a minor logging improvement.

for _, node := range nodes {
totalScore := 0.0
for tier := nta.hyperNodesTier.minTier; tier <= nta.hyperNodesTier.maxTier; tier++ {
tierScore := FullScore

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The initialization tierScore := FullScore seems logically incorrect for the goal of tiered bin-packing. When a node is not part of any hypernode in a given tier, it receives a perfect score (FullScore) for that tier. This rewards nodes for not being in a hypernode of a certain tier, which can lead to preferring nodes in higher-tiered hypernodes over nodes in lower-tiered ones, contradicting the goal of filling lower-tiered hypernodes first.

Consider changing the default score to ZeroScore to correctly penalize nodes that do not belong to a hypernode of a given tier. This would ensure that nodes within hypernodes of lower (more preferred) tiers are prioritized correctly.

Suggested change
tierScore := FullScore
tierScore := ZeroScore

@Tau721 Tau721 Jan 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When some node doesn't have a hypernode at the n-th tier, we can simply but equivalently consider that this node has a virtual hypernode at the n-th tier and its resources are fully utilized. Hence, we set its binpacking score to FullScore.

}
}
msg = append(msg, fmt.Sprintf("%s[%t]", HyperNodeBinPackNormalPodEnable, n.normalPodConfig.hyperNodeBinPackingEnable),
fmt.Sprintf("%s[%f]", HyperNodeBinPackNormalPodFading, n.normalPodConfig.hyperNodeBinPackingFading))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using %f for logging the hyperNodeBinPackingFading float value can result in unnecessary trailing zeros (e.g., 0.800000). Using %g would provide a more compact and readable representation (e.g., 0.8).

Suggested change
fmt.Sprintf("%s[%f]", HyperNodeBinPackNormalPodFading, n.normalPodConfig.hyperNodeBinPackingFading))
fmt.Sprintf("%s[%g]", HyperNodeBinPackNormalPodFading, n.normalPodConfig.hyperNodeBinPackingFading))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment on lines +280 to +290
for hyperNode := range ssn.HyperNodes {
if ssn.RealNodesSet[hyperNode].Has(node) {
if _, foundHyperNode := nta.hyperNodeResourceCache[hyperNode]; foundHyperNode {
for _, resource := range task.Resreq.ResourceNames() {
if _, foundResource := nta.hyperNodeResourceCache[hyperNode][resource]; foundResource {
nta.hyperNodeResourceCache[hyperNode][resource].used += task.Resreq.Get(resource)
}
}
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The current implementation iterates over all hypernodes (for hyperNode := range ssn.HyperNodes) for each task allocation to update the resource cache. This can be inefficient if the number of hypernodes is large.

A more performant approach would be to directly find the hypernode(s) a node belongs to and update only those. This could be achieved by building a reverse mapping from a node to its ancestor hypernodes during session open.

For example:

// In OnSessionOpen, build a map like:
// nodeToHyperNodes := map[string][]string
//
// Then in AllocateFunc:
if ancestors, found := nodeToHyperNodes[node]; found {
    for _, hyperNode := range ancestors {
        // update cache for hyperNode
    }
}

This would change the complexity from O(num_hypernodes) to O(depth_of_topology) for each allocation event.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually, the number of nodes is usually much larger than that of hypernodes, and the AllocateFunc or DeallocateFunc usually only uses the information of a few nodes. Therefore, caching the nodeToHypernodes map is considered not worth it.

@wangyang0616

Copy link
Copy Markdown
Member

Please supplement the corresponding capability scenarios and implementation descriptions in the design document.
Refer to Network Topology Aware Scheduling.md

*normalPodConfig
*hyperNodesTier
// hyperNodeResourceCache stores the resource status of hypernodes to avoid repeated calculation: hypernode -> resource -> status
hyperNodeResourceCache map[string]map[corev1.ResourceName]*resourceStatus

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Considering that this cache may be used frequently in future evolutions, it is recommended to fully initialize this cache in OnSessionOpen and replace the current lazy calculation implementation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perhaps issue #4899 could reuse this cache to pre-filtering HyperNodes during scheduiling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree with you, and I have fixed it.

@Tau721
Tau721 force-pushed the feat_hn-level_normal_pods_binpacking branch from 9715bad to 287704c Compare December 31, 2025 03:24
@Tau721
Tau721 force-pushed the feat_hn-level_normal_pods_binpacking branch 3 times, most recently from 2a0af7b to 9a8914b Compare January 3, 2026 14:42
@Tau721 Tau721 changed the title [WIP]Enhance network-topology-aware plugin to provide hypernode-level binpacking for normal pods Enhance network-topology-aware plugin to provide hypernode-level binpacking for normal pods Jan 6, 2026
@volcano-sh-bot volcano-sh-bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jan 6, 2026
Comment on lines +62 to +63
hyperNodeResourceCache map[string]map[corev1.ResourceName]*resourceStatus
}

@ouyangshengjia ouyangshengjia Jan 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
hyperNodeResourceCache map[string]map[corev1.ResourceName]*resourceStatus
}
hyperNodeResourceStat map[string]resourceStat // hypernode -> resourceStat
}
type resourceStat struct {
used *api.Resource
allocatable *api.Resource
}

It is recommended to use the api.Resource struct for future expansion.

type Resource struct {
MilliCPU float64
Memory float64
// ScalarResources
ScalarResources map[v1.ResourceName]float64
// MaxTaskNum is only used by predicates; it should NOT
// be accounted in other operators, e.g. Add.
MaxTaskNum int
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for your suggestion, and I have fixed it.

@Tau721
Tau721 force-pushed the feat_hn-level_normal_pods_binpacking branch from 746d048 to 1e59e7a Compare January 8, 2026 06:37
@Tau721
Tau721 force-pushed the feat_hn-level_normal_pods_binpacking branch from adf7420 to 1de4f0c Compare January 8, 2026 06:58
@Tau721
Tau721 force-pushed the feat_hn-level_normal_pods_binpacking branch 2 times, most recently from 834591e to 2c697c0 Compare January 8, 2026 13:05
args.GetFloat64(&config.hyperNodeBinPackingFading, HyperNodeBinPackNormalPodFading)
// config.hyperNodeBinPackingFading could be 0, which implies only the hypernodes of tier 1 affect the pod binpacking scores
if config.hyperNodeBinPackingFading < 0 {
config.hyperNodeBinPackingFading = 0.8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it would be better to use constant instead

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for you review, and I have fixed it.

Comment on lines +698 to +704
func (nta *networkTopologyAwarePlugin) normalizeFinalScore(scores map[string]float64) map[string]float64 {
normalizedScores := make(map[string]float64)
for name, score := range scores {
normalizedScores[name] = float64(k8sFramework.MaxNodeScore) * float64(nta.weight.GlobalWeight) * score
}
return normalizedScores
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think the processing of this function can be called normalize.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How about changing it to scale?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agreed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

if totalTierWeight <= 0 {
// This should not happen, since there are at least one tier and its weight is one
klog.Warningf("the total tier weight of plugin %s should be greater than zero, but got %g", PluginName, totalTierWeight)
return nil, fmt.Errorf("the total tier weight of plugin %s should be greater than zero, but got %g", PluginName, totalTierWeight)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we can just leave a log here an return empty result. Considering that when we return an error in NodeOrderFn, the allocate process would be broken and the pod won't be scheduled.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree, and I have fixed it.

for _, node := range nodes {
totalScore := 0.0
for tier := nta.hyperNodesTier.minTier; tier <= nta.hyperNodesTier.maxTier; tier++ {
tierScore := FullScore

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is recommended to add a comment here: Prefers to schedule pods on nodes that do not belong to any hypernode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@ouyangshengjia

Copy link
Copy Markdown
Contributor

/ok-to-test

@volcano-sh-bot volcano-sh-bot added the ok-to-test Indicates a non-member PR verified by an org member that is safe to test. label Jan 13, 2026
// HyperNodeBinPackNormalPodEnable is the key for whether to enable hypernode-level binpacking for pods without network topology
HyperNodeBinPackNormalPodEnable = "hypernode.binpack.normal-pod.enable"
// HyperNodeBinPackNormalPodFading is the key for tier weight fading parameter for pods without network topology
HyperNodeBinPackNormalPodFading = "hypernode.binpack.normal-pod.fading"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you should also update the design doc and user guide doc to teach users how to use it and what does they mean

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok. I will try my best to finish it early.

Comment thread pkg/scheduler/plugins/network-topology-aware/network_topology_aware.go Outdated
klog.V(5).Infof("Leaving networkTopologyAware plugin ...")
}()
nta.hyperNodesTier.init(ssn.HyperNodesTiers)
nta.initHyperNodeResourceCache(ssn)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The hyperNodeResourceCache is initialized each time a session is started. Is there any impact on a large cluster? Are there any performance test for this function?

@Tau721 Tau721 Jan 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually, I have evaluated the time efficiency of this initHyperNodeResourceCache function through the Test_initHyperNodeResourceCache function in the test file. The results demonstrate that aiming at a cluster with 2000 nodes, 200 hypernodes of tier 1, 40 hypernodes of tier 2, 5 hypernodes of tier 3, 1 hypernode of tier 4, and 3 resource types, it takes only about 3 milliseconds to initialize this hyperNodeResourceCache. Such time cost is totally acceptable.

Besides, I have also evaluated the time efficiency of the new batchNodeOrderFnForNormalPods function (which calculates the hypernode-level binpacking score for normal pods) through the Test_batchNodeOrderFnForNormalPods function in the test file. The results show that aiming at the same cluster, it takes about 10~15 milliseconds to invoke this function to score the whole 2000 nodes for one task. Such time cost is also acceptable.

@Tau721
Tau721 force-pushed the feat_hn-level_normal_pods_binpacking branch 3 times, most recently from 35cc361 to 2283848 Compare January 15, 2026 13:47
@ouyangshengjia

Copy link
Copy Markdown
Contributor

/lgtm
defers to @JesseStutler

@volcano-sh-bot volcano-sh-bot added the lgtm Indicates that a PR is ready to be merged. label Jan 16, 2026
@JesseStutler

Copy link
Copy Markdown
Member

Waiting for @Tau721 #4889 (comment) finish the doc and then we can get merged

@volcano-sh-bot volcano-sh-bot removed the lgtm Indicates that a PR is ready to be merged. label Jan 20, 2026
@Tau721
Tau721 force-pushed the feat_hn-level_normal_pods_binpacking branch 3 times, most recently from c6a1aa9 to 3aa0509 Compare January 20, 2026 05:31
Signed-off-by: caotuo721 <caotuo721@yeah.net>
Signed-off-by: caotuo721 <caotuo721@yeah.net>
@Tau721
Tau721 force-pushed the feat_hn-level_normal_pods_binpacking branch from 3aa0509 to 9728115 Compare January 20, 2026 05:32
@ouyangshengjia

Copy link
Copy Markdown
Contributor

/lgtm

@volcano-sh-bot volcano-sh-bot added the lgtm Indicates that a PR is ready to be merged. label Jan 20, 2026

@JesseStutler JesseStutler left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

/approve

@volcano-sh-bot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: JesseStutler

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

The pull request process is described here

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

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

@volcano-sh-bot volcano-sh-bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 20, 2026
@volcano-sh-bot
volcano-sh-bot merged commit ad77883 into volcano-sh:master Jan 20, 2026
22 of 23 checks passed
@Tau721
Tau721 deleted the feat_hn-level_normal_pods_binpacking branch January 21, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. kind/feature Categorizes issue or PR as related to a new feature. lgtm Indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

network-topology-aware should provide hypernode-level binpacking for normal pods

6 participants