Enhance network-topology-aware plugin to provide hypernode-level binpacking for normal pods - #4889
Conversation
|
Welcome @Tau721! It looks like this is your first PR to volcano-sh/volcano 🎉 |
Summary of ChangesHello @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 Highlights
🧠 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 AssistThe 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
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 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
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| tierScore := FullScore | |
| tierScore := ZeroScore |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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).
| fmt.Sprintf("%s[%f]", HyperNodeBinPackNormalPodFading, n.normalPodConfig.hyperNodeBinPackingFading)) | |
| fmt.Sprintf("%s[%g]", HyperNodeBinPackNormalPodFading, n.normalPodConfig.hyperNodeBinPackingFading)) |
| 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) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
Please supplement the corresponding capability scenarios and implementation descriptions in the design document. |
| *normalPodConfig | ||
| *hyperNodesTier | ||
| // hyperNodeResourceCache stores the resource status of hypernodes to avoid repeated calculation: hypernode -> resource -> status | ||
| hyperNodeResourceCache map[string]map[corev1.ResourceName]*resourceStatus |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Perhaps issue #4899 could reuse this cache to pre-filtering HyperNodes during scheduiling.
There was a problem hiding this comment.
I agree with you, and I have fixed it.
9715bad to
287704c
Compare
2a0af7b to
9a8914b
Compare
| hyperNodeResourceCache map[string]map[corev1.ResourceName]*resourceStatus | ||
| } |
There was a problem hiding this comment.
| 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.
volcano/pkg/scheduler/api/resource_info.go
Lines 58 to 68 in 703a8f3
There was a problem hiding this comment.
Thanks for your suggestion, and I have fixed it.
746d048 to
1e59e7a
Compare
adf7420 to
1de4f0c
Compare
834591e to
2c697c0
Compare
| 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 |
There was a problem hiding this comment.
it would be better to use constant instead
There was a problem hiding this comment.
Thanks for you review, and I have fixed it.
| 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 | ||
| } |
There was a problem hiding this comment.
I don't think the processing of this function can be called normalize.
There was a problem hiding this comment.
How about changing it to scale?
| 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
It is recommended to add a comment here: Prefers to schedule pods on nodes that do not belong to any hypernode.
|
/ok-to-test |
| // 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" |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Ok. I will try my best to finish it early.
| klog.V(5).Infof("Leaving networkTopologyAware plugin ...") | ||
| }() | ||
| nta.hyperNodesTier.init(ssn.HyperNodesTiers) | ||
| nta.initHyperNodeResourceCache(ssn) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
35cc361 to
2283848
Compare
|
/lgtm |
|
Waiting for @Tau721 #4889 (comment) finish the doc and then we can get merged |
c6a1aa9 to
3aa0509
Compare
Signed-off-by: caotuo721 <caotuo721@yeah.net>
Signed-off-by: caotuo721 <caotuo721@yeah.net>
3aa0509 to
9728115
Compare
|
/lgtm |
|
[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 DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
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?