fix(api): reject non-integral bounds for Int search spaces - #3920
fix(api): reject non-integral bounds for Int search spaces#3920sanskar-singh-2403 wants to merge 1 commit into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
UniformSpace and LogUniformSpace both carry a `type` field (Int or Float),
but `min` and `max` are Double strings constrained only by the Double
pattern and by `double(self.min) < double(self.max)`. Nothing connected
`type: Int` to integral bounds, so an Int parameter with fractional bounds,
or with a range containing no integer at all, passed admission.
`{min: "1.2", max: "1.8", type: Int}` was accepted even though the search
space is unsatisfiable. The failure was deferred to the suggestion backend,
where Katib's search space conversion calls Python `int()` directly on the
bounds and raises an opaque ValueError from inside the suggestion container
with nothing pointing at the offending field.
Add one CEL rule to each type, guarded on `self.type != 'Int'` so the Float
path is untouched. `type` defaults to Float before CEL runs, so the guard is
always evaluable.
The rule also bounds the magnitude at 2^53. Beyond that a float64 cannot
represent consecutive integers, so integrality stops being a meaningful
property of the value, and CEL's `int()` conversion would fail on values
outside the int64 range. Keeping the check inside the same rule means an
out-of-range Int bound is reported by a message that describes the actual
problem.
This affects only the CRD schema. XValidation markers do not flow into
zz_generated.openapi.go or swagger.json, so the regenerated surface is the
two CRD YAMLs.
Fixes kubeflow#3906
Signed-off-by: Sanskar Singh <sanskarsinghty1234@gmail.com>
2a6b553 to
c471ab3
Compare
There was a problem hiding this comment.
Thanks @sanskar-singh-2403 for addressing this validation gap. The overall direction and test coverage look good, but I found one blocking contract issue.
The new rule accepts "1.0" and "1e2" as integral values, while the cited Katib path still calls int() on the original strings. Since CEL validation does not rewrite the stored bounds, both values will still fail at runtime with ValueError.
The float round-trip can also misclassify fractional input because of IEEE-754 rounding. For example, double("1.0000000000000001") becomes 1.0, so this value can pass the rule even though its original value is non-integral. The 2^53 magnitude limit does not prevent precision loss like this.
Could we define the intended wire-format contract and then either:
- require canonical integer strings for
Intbounds using an anchored integer regex, or - normalize/parse the values exactly before passing them to the suggestion provider?
Please also add regression coverage for "1.0000000000000001" and update the "1.0"/"1e2" expectations according to the chosen contract.
PTAL : @andreyvelich
|
@HarshPopat23: changing LGTM is restricted to collaborators DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
What this PR does / why we need it
UniformSpaceandLogUniformSpaceboth carry atypefield (IntorFloat), but theirminandmaxareDoublestrings constrained only by theDoublepattern and by the CEL ruledouble(self.min) < double(self.max). Nothing connectedtype: Intto integral bounds, so all of these were accepted by the API server:1.58.71.21.81e-31e20.510110.5The
1.2/1.8case is the sharpest one: there is no integer in[1.2, 1.8], so the search space is unsatisfiable, and the object is still created.The failure was then deferred to the suggestion backend. Katib's search space conversion takes the INTEGER branch and calls Python
int()directly on the bounds:https://github.com/kubeflow/katib/blob/master/pkg/suggestion/v1beta1/internal/search_space.py#L53-L55
Python
int()rejects every fractional and scientific-notation string the Trainer API accepts, so the user gets an opaqueValueErrortraceback from inside the suggestion container with nothing pointing at the offending field. This is the same failure class that #3862 exists to eliminate for grid search.The KEP already puts this in scope.
proposals/2605-optimization-job-crd/README.mdsays the discriminated union exists so that mathematical CEL validations (double(),int()) are possible. Thedouble()half shipped; theint()half was never implemented.Design notes
One CEL rule per type, guarded on
self.type != 'Int'. The Float path is untouched.typeis defaulted toFloatbefore CEL runs, so the guard is always evaluable, including when the user omitstypeentirely.Integrality is tested by a float round-trip,
double(x) == double(int(double(x))), so"1","1.0"and"1e2"are all correctly treated as whole numbers. The check is on the value, not on its spelling.The rule also clamps magnitude to 2^53, and this is load-bearing rather than decorative. CEL's
int()conversion errors on a double outside int64 range, and an erroring rule surfaces astype conversion error from 'double' to 'int' evaluating rule: <message>, which names the rule but describes the wrong problem. Folding the range check into the same rule means an out-of-rangeIntbound gets a message about its actual problem. 2^53 rather than int64 max because above 2^53 a float64 cannot represent consecutive integers, so "is this a whole number" stops being a meaningful question about the user's input.Not addressed here: a bound above
1e308overflowsdouble()in the pre-existingmin < maxrule and reportstype conversion error ... evaluating rule: min must be strictly less than max, which names ordering when the problem is magnitude. That comes from theDoubletype'sMaxLength=64pattern, affectsFloatequally, and is better fixed onDoubleitself. I noted it in #3906 and it is left for a follow-up.Testing
New envtest table in
test/integration/webhooks/optimizationjob_int_bounds_test.go, 16 cases:1.58.71.21.80.510110.51e-31e211e300181.08.01e21e3-8-10.0010.10.0010.11.58.71.21.8110240.00010.1Generated assets
XValidationmarkers do not flow intozz_generated.openapi.goorswagger.json, so the regenerated surface is only the two CRD YAMLs (manifests/base/crds/and the Helm chart copy). Produced bymake manifests.Fixes #3906