@@ -6,6 +6,7 @@ package resources
66import (
77 "fmt"
88 "strconv"
9+ "strings"
910
1011 v1 "k8s.io/api/core/v1"
1112 "k8s.io/apimachinery/pkg/api/resource"
@@ -18,6 +19,10 @@ import (
1819// the gpu-fraction-container-name annotation, and NvFractions annotations. It is
1920// configmap-agnostic and shared by the admission plugins.
2021func ValidateGPUFractionRequest (pod * v1.Pod ) error {
22+ if err := validateGpuMemoryPortionLimitAnnotation (pod ); err != nil {
23+ return err
24+ }
25+
2126 req , err := ParsePodGPUFractionRequest (pod )
2227 if err != nil {
2328 return err
@@ -90,6 +95,73 @@ func validateGpuMemoryNvFractionsConsistency(pod *v1.Pod) error {
9095 return nil
9196}
9297
98+ // validateGpuMemoryPortionLimitAnnotation validates the kai.scheduler
99+ // gpu-memory.portion.limit annotation: it may only be used together with
100+ // gpu-fraction, on the same container, as a fraction strictly greater than
101+ // gpu-fraction and strictly smaller than 1.0, with at most 5 decimal digits.
102+ func validateGpuMemoryPortionLimitAnnotation (pod * v1.Pod ) error {
103+ containerName , rawValue , found := ExtractGpuMemoryPortionLimitAnnotation (pod )
104+ if ! found {
105+ return nil
106+ }
107+ annotationKey := CalcGpuMemoryPortionLimitAnnotationForContainer (containerName )
108+
109+ gpuFractionStr , hasGpuFraction := pod .Annotations [constants .GpuFraction ]
110+ if ! hasGpuFraction || gpuFractionStr == "" {
111+ return fmt .Errorf ("%s annotation can only be used together with the %s annotation" ,
112+ annotationKey , constants .GpuFraction )
113+ }
114+
115+ if err := validateGpuMemoryPortionLimitContainerName (pod , containerName , annotationKey ); err != nil {
116+ return err
117+ }
118+
119+ gpuFraction , err := strconv .ParseFloat (gpuFractionStr , 64 )
120+ if err != nil {
121+ return fmt .Errorf ("gpu-fraction annotation value must be a positive number smaller than 1.0" )
122+ }
123+
124+ if err := validatePortionLimitDecimalPrecision (rawValue , annotationKey ); err != nil {
125+ return err
126+ }
127+
128+ portionLimit , err := strconv .ParseFloat (rawValue , 64 )
129+ if err != nil || portionLimit <= 0 || portionLimit >= 1 {
130+ return fmt .Errorf ("%s annotation value must be a positive number smaller than 1.0" , annotationKey )
131+ }
132+
133+ if portionLimit <= gpuFraction {
134+ return fmt .Errorf ("%s annotation value (%s) must be greater than %s annotation value (%s)" ,
135+ annotationKey , rawValue , constants .GpuFraction , gpuFractionStr )
136+ }
137+
138+ return nil
139+ }
140+
141+ func validateGpuMemoryPortionLimitContainerName (pod * v1.Pod , containerName , annotationKey string ) error {
142+ if legacyContainerName , hasGpuFractionContainerName := pod .Annotations [constants .GpuFractionContainerName ]; hasGpuFractionContainerName {
143+ if legacyContainerName != containerName {
144+ return fmt .Errorf ("%s annotation value %s does not match container name %s in %s annotation" ,
145+ constants .GpuFractionContainerName , legacyContainerName , containerName , annotationKey )
146+ }
147+ return nil
148+ }
149+
150+ if len (pod .Spec .Containers ) == 0 || pod .Spec .Containers [0 ].Name != containerName {
151+ return fmt .Errorf ("%s annotation container name %s does not match the gpu-fraction target container" ,
152+ annotationKey , containerName )
153+ }
154+ return nil
155+ }
156+
157+ func validatePortionLimitDecimalPrecision (rawValue , annotationKey string ) error {
158+ _ , fraction , hasDecimalPoint := strings .Cut (rawValue , "." )
159+ if hasDecimalPoint && len (fraction ) > 5 {
160+ return fmt .Errorf ("%s annotation value must have at most 5 digits after the decimal point" , annotationKey )
161+ }
162+ return nil
163+ }
164+
93165func validateNvFractionsAnnotations (hasNvFractionsAnnotation bool , pod * v1.Pod ) error {
94166 if ! hasNvFractionsAnnotation {
95167 return nil
0 commit comments