Skip to content

Preserve input dtype in torch floor_divide converter#2681

Open
john-rocky wants to merge 5 commits into
apple:mainfrom
john-rocky:fix-floor-divide-int-dtype
Open

Preserve input dtype in torch floor_divide converter#2681
john-rocky wants to merge 5 commits into
apple:mainfrom
john-rocky:fix-floor-divide-int-dtype

Conversation

@john-rocky
Copy link
Copy Markdown
Contributor

Summary

  • PyTorch's torch.floor_divide returns the input dtype (int for int inputs, float for float inputs), but the converter unconditionally cast the result to fp32. The misleading inline comment claimed PyTorch "always returns fp32, even if the inputs are int" — this is not true.
  • Drop the unconditional mb.cast(..., dtype='fp32'); mb.floor_div already produces an output of the promoted input dtype.

Test plan

  • pytest test_torch_ops.py::TestActivation::test_floor_divide — 8 cases across int32/float32 inputs, both backends.
  • End-to-end mlpackage.predict on the issue repro: int32 inputs now produce an int32 output (was fp32 before).

Fixes #2570.

Copy link
Copy Markdown
Collaborator

@TobyRoseman TobyRoseman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something isn't right here. Your new unit test passes even without your changes to ops.py.

Comment thread coremltools/converters/mil/frontend/torch/test/test_torch_ops.py
john-rocky added 2 commits May 6, 2026 09:25
PyTorch's `torch.floor_divide` returns the input dtype (int for int
inputs, float for float inputs), but the converter unconditionally
cast the result to fp32. The misleading inline comment claimed PyTorch
"always returns fp32, even if the inputs are int" — this is not true.

Drop the unconditional `mb.cast(..., dtype='fp32')`; `mb.floor_div`
already produces an output of the promoted input dtype.

Fixes apple#2570.
The previous test only compared values via run_compare_torch, which
passed even without the ops.py change because [10/3, -10/3, 7/2, -7/2]
= [3, -4, 3, -4] is representable identically in int32 and fp32.
Inspect the MIL main function output var's dtype directly so the test
truly pins the dtype-preserving behavior the PR claims.
@john-rocky john-rocky force-pushed the fix-floor-divide-int-dtype branch from 2b82908 to 99e2dcb Compare May 6, 2026 00:26
@john-rocky
Copy link
Copy Markdown
Contributor Author

Thanks for the catch — fixed in 99e2dcb. The original test only used assert_allclose, which passed regardless because [10/3, -10/3, 7/2, -7/2] = [3, -4, 3, -4] are representable identically in int32 and fp32. The new commit asserts _mil_program.functions["main"].outputs[0].dtype == types.int32 / types.fp32 directly, so the test now fails on the un-fixed converter. Also rebased onto latest main per your other comment.

Per @TobyRoseman's review on apple#2681: the new floor_divide regression
test was sitting under `TestActivation`, which is a poor fit. Move it
into its own `TestFloorDivide(TorchBaseTest)` class, placed between
the trailing `test_div` of `TestActivation` and `TestElementWiseUnary`.
No behavior change — same parametrization, same assertions.

AST-verified: `test_floor_divide` is now inside `TestFloorDivide` at
line 6686.
@john-rocky
Copy link
Copy Markdown
Contributor Author

Moved `test_floor_divide` into its own `TestFloorDivide(TorchBaseTest)` class in `ae07088` per your earlier review point on placement. AST-verified the method is now under `TestFloorDivide` rather than `TestActivation`; no behavior change.

@john-rocky
Copy link
Copy Markdown
Contributor Author

Thanks @TobyRoseman — both points addressed:

  • Test passed without the change: the numerical comparison alone passed for either path because the quotient ([3, -4, 3, -4]) is representable in int32 and fp32 alike. I added an explicit assertion on the converted program's output dtype — an int input must now produce an int32 output (it was fp32 before this fix), so the test fails without the ops.py change. (A float input only needs to stay a float type, since the width tracks the fp16/fp32 backend.)
  • Test placement: moved out of TestActivation into a dedicated TestFloorDivide class.

Verified locally that int inputs now yield an int32 model output where the old converter emitted fp32.

input_as_shape=False,
expected_results=out,
)
# Pin the output dtype. A numerical comparison alone passes even
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than access underscore/private members to inspect the MIL type, let's just get predictions from the converted model and verify they are ints rather than floats. It's simpler and more directly matches the user experience.

@TobyRoseman
Copy link
Copy Markdown
Collaborator

@john-rocky
Copy link
Copy Markdown
Contributor Author

Done — switched to checking the prediction's dtype instead of the MIL program internals (the run_compare_torch prediction output).

One wrinkle worth noting: the NeuralNetwork backend always serializes outputs as float, so the int-vs-float distinction is only observable on the ML Program backend — I guard the integer assertion to backend[0] == "mlprogram". The float case is checked on every backend.

Verified locally: every TestFloorDivide parametrization passes on this branch, and the mlprogram int cases fail on main (the prediction comes back fp32 there), so the test still catches the regression.

@TobyRoseman
Copy link
Copy Markdown
Collaborator

Updated CI: https://gitlab.com/coremltools1/coremltools/-/pipelines/2553826151

Assuming this passes, I will merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

torch.floordivide returns wrong dtype when inputs are integers

2 participants