Skip to content

Commit 099fe7b

Browse files
committed
fix: sync moved packages with KAI-Scheduler main; add utilities/math
Carries the DRA GPU-count overflow fix (#1874) into utilities/resources, which requires the new utilities/math SaturatingAdd helper. Also re-syncs scheduling/v2alpha2 PodGroup types and podgrouper/constants with main so the module reflects current upstream. Prevents consumers repointed at the api module from silently losing these upstream fixes. Signed-off-by: SiorMeir <msior@nvidia.com>
1 parent b52bbd7 commit 099fe7b

11 files changed

Lines changed: 190 additions & 17 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
kind: Added
2+
body: |-
3+
`kai.scheduler/v1` API group (`kai/v1`): `Config` and `SchedulingShard` types plus their component sub-packages, with generated CRD manifests. `usagedb` config structs (`UsageDBConfig`, `UsageParams`, `WindowType`), `podgrouper/constants`, and `utilities/math` (`SaturatingAdd`).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
kind: Changed
2+
body: |-
3+
Re-synced `utilities/resources` (DRA GPU-count overflow fix), `scheduling/v2alpha2` PodGroup types, and `podgrouper/constants` with KAI-Scheduler main.

CHANGELOG.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,6 @@ All notable changes to api will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
9-
10-
### Added
11-
12-
- **`kai.scheduler/v1` API group** (`kai/v1`) — Config and SchedulingShard operator types plus their
13-
component sub-packages (`admission`, `binder`, `common`, `node_scale_adjuster`,
14-
`numa_placement_exporter`, `pod_group_controller`, `pod_grouper`, `prometheus`, `queue_controller`,
15-
`scheduler`), with `config` and `schedulingshard` CRD manifests.
16-
- **`usagedb`** — usage-database config structs (`UsageDBConfig`, `UsageParams`, `WindowType`) consumed by
17-
SchedulingShard and external API users.
18-
- **`podgrouper/constants`** — PodGroup labeling/annotation constants for external integrators.
19-
208
## [v0.1.0]
219

2210
Initial release of the standalone `github.com/kai-scheduler/api` module, seeded from KAI-Scheduler `main`.

config/crd/scheduling.run.ai_podgroups.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ spec:
101101
-1 and 1
102102
format: int32
103103
type: integer
104+
stalenessGracePeriod:
105+
description: |-
106+
StalenessGracePeriod is the minimum duration a stale PodGroup it allowed to remain in stale
107+
status before stale workloads may be evicted to make room. Negative values disable stale gang
108+
eviction for this PodGroup. Defaults to the scheduler's global staleness grace period.
109+
type: string
104110
subGroups:
105111
description: SubGroups defines finer-grained subsets of pods within
106112
the PodGroup with individual scheduling constraints

podgrouper/constants/constants.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const (
1010
PreemptibilityLabelKey = "kai.scheduler/preemptibility"
1111
UserLabelKey = "user"
1212

13-
PreemptionDelayAnnotationKey = "kai.scheduler/preemption-delay"
13+
PreemptionDelayAnnotationKey = "kai.scheduler/preemption-delay"
14+
StalenessGracePeriodAnnotationKey = "kai.scheduler/staleness-grace-period"
1415

1516
BuildPriorityClass = "build"
1617
TrainPriorityClass = "train"

scheduling/v2alpha2/podgroup_types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ type PodGroupSpec struct {
8282
// allocation into free capacity, nor the PodGroup's own evictability.
8383
// +optional
8484
PreemptionDelay *metav1.Duration `json:"preemptionDelay,omitempty" protobuf:"bytes,9,opt,name=preemptionDelay"`
85+
86+
// StalenessGracePeriod is the minimum duration a stale PodGroup it allowed to remain in stale
87+
// status before stale workloads may be evicted to make room. Negative values disable stale gang
88+
// eviction for this PodGroup. Defaults to the scheduler's global staleness grace period.
89+
// +optional
90+
StalenessGracePeriod *metav1.Duration `json:"stalenessGracePeriod,omitempty" protobuf:"bytes,10,opt,name=stalenessGracePeriod"`
8591
}
8692

8793
// Preemptibility defines whether this PodGroup can be preempted
@@ -128,6 +134,16 @@ func ParsePreemptionDelay(value string) (*metav1.Duration, error) {
128134
return &metav1.Duration{Duration: delay}, nil
129135
}
130136

137+
// ParseStalenessGracePeriod parses a staleness grace period string (e.g. "-10s" "30s", "5m").
138+
// Returns an error for invalid values.
139+
func ParseStalenessGracePeriod(value string) (*metav1.Duration, error) {
140+
stale, err := time.ParseDuration(value)
141+
if err != nil {
142+
return nil, err
143+
}
144+
return &metav1.Duration{Duration: stale}, nil
145+
}
146+
131147
type SubGroup struct {
132148
// Name uniquely identifies the SubGroup within the PodGroup.
133149
// Must be a valid DNS label (RFC 1123).

scheduling/v2alpha2/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

utilities/math/saturating.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2025 NVIDIA CORPORATION
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package math holds dependency-light arithmetic helpers that the scheduler's
5+
// resource model can use without pulling in Kubernetes or DRA client packages.
6+
package math
7+
8+
import stdmath "math"
9+
10+
// SaturatingAdd returns a + b, clamped to [math.MinInt64, math.MaxInt64]
11+
// on overflow instead of wrapping around to the opposite sign.
12+
func SaturatingAdd(a, b int64) int64 {
13+
sum := a + b
14+
// Overflow can only happen when both operands share a sign and the sign of
15+
// the result flips.
16+
if a > 0 && b > 0 && sum < 0 {
17+
return stdmath.MaxInt64
18+
}
19+
if a < 0 && b < 0 && sum >= 0 {
20+
return stdmath.MinInt64
21+
}
22+
return sum
23+
}

utilities/math/saturating_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2025 NVIDIA CORPORATION
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package math
5+
6+
import (
7+
stdmath "math"
8+
"testing"
9+
)
10+
11+
func TestSaturatingAdd(t *testing.T) {
12+
cases := []struct {
13+
name string
14+
a, b int64
15+
want int64
16+
}{
17+
{"normal sum", 2, 3, 5},
18+
{"positive overflow saturates to MaxInt64", stdmath.MaxInt64, stdmath.MaxInt64, stdmath.MaxInt64},
19+
{"positive overflow by one", stdmath.MaxInt64, 1, stdmath.MaxInt64},
20+
{"negative overflow saturates to MinInt64", stdmath.MinInt64, stdmath.MinInt64, stdmath.MinInt64},
21+
{"mixed signs do not overflow", stdmath.MaxInt64, stdmath.MinInt64, -1},
22+
}
23+
for _, tc := range cases {
24+
t.Run(tc.name, func(t *testing.T) {
25+
if got := SaturatingAdd(tc.a, tc.b); got != tc.want {
26+
t.Fatalf("SaturatingAdd(%d, %d) = %d, want %d", tc.a, tc.b, got, tc.want)
27+
}
28+
})
29+
}
30+
}

utilities/resources/dra.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
draclient "k8s.io/dynamic-resource-allocation/client"
2424
resourceinstall "k8s.io/kubernetes/pkg/apis/resource/install"
2525
"sigs.k8s.io/controller-runtime/pkg/client"
26+
27+
commonmath "github.com/kai-scheduler/api/utilities/math"
2628
)
2729

2830
func GetResourceClaimName(pod *v1.Pod, podClaim *v1.PodResourceClaim) (string, error) {
@@ -129,7 +131,7 @@ func ExtractDRAGPUResourcesFromClaims(podResourceClaims []*resourceapi.ResourceC
129131
// Find the DeviceClassName for this claim
130132
deviceClassName := getGPUDeviceClassNameFromClaim(claim)
131133
if deviceClassName != "" {
132-
deviceClassCounts[deviceClassName] += gpuCount
134+
deviceClassCounts[deviceClassName] = commonmath.SaturatingAdd(deviceClassCounts[deviceClassName], gpuCount)
133135
}
134136
}
135137
}
@@ -163,6 +165,10 @@ func getGPUDeviceClassNameFromClaim(claim *resourceapi.ResourceClaim) string {
163165

164166
// countGPUDevicesFromClaim counts GPU devices from a ResourceClaim.
165167
// Returns the total count of GPU devices requested by this claim.
168+
//
169+
// Exactly.Count is user-controlled (the apiserver only enforces > 0), so counts
170+
// are summed with SaturatingAdd to keep a very large total from wrapping
171+
// into a negative GPU request in downstream quota accounting.
166172
func countGPUDevicesFromClaim(claim *resourceapi.ResourceClaim) int64 {
167173
totalCount := int64(0)
168174

@@ -178,16 +184,16 @@ func countGPUDevicesFromClaim(claim *resourceapi.ResourceClaim) int64 {
178184
switch request.Exactly.AllocationMode {
179185
case resourceapi.DeviceAllocationModeExactCount:
180186
if request.Exactly.Count > 0 {
181-
totalCount += request.Exactly.Count
187+
totalCount = commonmath.SaturatingAdd(totalCount, request.Exactly.Count)
182188
} else {
183189
// Default to 1 if Count is not specified for ExactCount mode
184-
totalCount += 1
190+
totalCount = commonmath.SaturatingAdd(totalCount, 1)
185191
}
186192
case resourceapi.DeviceAllocationModeAll:
187193
// For "All" mode, we can't determine the exact count without allocation info.
188194
// For bookkeeping purposes, we'll treat it as requesting 1 device.
189195
// This is a conservative estimate for queue resource tracking.
190-
totalCount += 1
196+
totalCount = commonmath.SaturatingAdd(totalCount, 1)
191197
default:
192198
// Unknown allocation mode, skip this request
193199
continue

0 commit comments

Comments
 (0)