Keep floor_div(x, 1) for float inputs in noop_elimination - #2762
Keep floor_div(x, 1) for float inputs in noop_elimination#2762winklemad wants to merge 2 commits into
Conversation
noop_elimination removed floor_div(x, y=1) as an identity, grouped with real_div and pow. But floor_div rounds toward negative infinity, so floor_div(x, 1) == floor(x), which equals x only when x is already integral. For a float input the pass silently dropped the floor and changed the model's numerics (e.g. 1.5 was carried through as 1.5 instead of 1.0). Restrict the floor_div no-op to integer inputs; float inputs keep the op, and the integer optimization is preserved. Updated test_elementwise_elimination (its fp32 input now correctly keeps floor_div) and added test_floor_div_elimination_by_dtype covering the integer-removed and float-kept cases.
| ], | ||
| ) | ||
| def test_floor_div_elimination_by_dtype(self, dtype, one, expected): | ||
| # floor_div(x, 1) == floor(x) is a no-op only when x is integral. It must be |
There was a problem hiding this comment.
This unit test doesn't really demonstrate the issue. Could you convert a toy and then show that the prediction/output of the converted program is correct? That model should produce an incorrect result without your fix.
test_floor_div_elimination_by_dtype only checks the op graph. Add test_floor_div_float_prediction_is_floored, which converts a toy program and compares the prediction against floor(x) + 1. add(_, 1) keeps the floor_div interior so noop_elimination is free to remove it: without the dtype guard the floor_div is eliminated and the model returns x + 1, so this test fails before the fix and passes after.
|
Thanks @TobyRoseman — added The |
|
@winklemad - Thanks for the update. Changes look good. CI: https://gitlab.com/coremltools1/coremltools/-/pipelines/2712923781 |
Fixes #2761.
Problem
noop_eliminationremovedfloor_div(x, y=1)as an identity, grouped withreal_divandpow. Butfloor_divrounds toward negative infinity, sofloor_div(x, 1) == floor(x), which equalsxonly whenxis already integral. For a float input the pass silently dropped the floor and changed the model's numerics — no error, just wrong results (e.g.1.5carried through as1.5instead of1.0). The pass is in the common (default) pipeline.x / 1 == xandx ** 1 == xhold for all reals;floor_div(x, 1) == floor(x)does not.Fix
Split
floor_divout of the group and remove it only when the input is integer (types.is_int(op.x.dtype)); float inputs keep the op. The integer optimization is preserved.Tests
test_elementwise_eliminationused an fp32 input yet assertedfloor_div(x, 1)was removed — it now correctly expects the op to be kept. Addedtest_floor_div_elimination_by_dtypecovering both the integer-removed and float-kept cases. Verified the pass behavior locally (appliedcommon::noop_eliminationdirectly): float keeps floor_div, integer removes it, and reverting the fix reproduces the float miscompilation. The full test module runs under the project's CI (it imports torch/tensorflow, which aren't needed to exercise the pass itself).