Skip to content

Keep float scalars exact in float64 ops in ScalarToTensorPass - #24168

Merged
JacobSzwejbka merged 2 commits into
pytorch:mainfrom
usmanmateen:fix/scalar-to-tensor-float64
Oct 29, 2026
Merged

JacobSzwejbka merged 2 commits into
pytorch:mainfrom
usmanmateen:fix/scalar-to-tensor-float64

Conversation

@usmanmateen

@usmanmateen usmanmateen commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #24167.

ScalarToTensorPass turns a Python scalar operand into torch.tensor(value), which is float32 for a float. In a float64 program, RemoveMixedTypeOperators then casts that constant to float64, but the value has already been rounded, so x * 0.1 multiplies by 0.10000000149011612. x + 0.1, x - 0.1, x / 0.1, 0.1 - x and torch.deg2rad(x) were off by about 1e-9 to 1.5e-8 relative.

When the op has a float64 tensor operand, the pass now creates float scalars as float64 constants. Those graphs also lose the _to_dim_order_copy that cast the constant. Float32, float16 and integer graphs get the same constants as before.

Test plan

test_scalar_to_tensor_pass_keeps_float64_scalars in exir/tests/test_passes.py runs (x * 0.1 + 0.3) / 0.7 on a float64 input through ScalarToTensorPass and RemoveMixedTypeOperators and checks the result equals eager exactly. It fails before this change and passes after.

I ran it against executorch 1.5.1 and torch 2.14.1+cpu on Linux x86_64 (WSL2), with this file swapped in (the pass is the same on main). Of the 37 tests in test_passes.py matching scalar, mixed, constant_prop, to_copy, debug_handle, symint or dim_order, the same ones pass before and after apart from the new test. Five fail on 1.5.1 both with and without this change, because main's test file needs newer constant_prop_pass and memory-format code than that release has. Lowering x * 0.1, x + 0.1, x / 0.1, 0.1 - x and torch.deg2rad(x) with to_edge(...).to_executorch() and running them through the portable pybindings now matches eager for float64, and gives the same graph and output as before for float32, float16, and int32 inputs. ufmt (black 24.4.2, usort 1.0.8.post1) and flake8 pass on the changed files.

This PR was authored with AI assistance (Claude Code).

cc @nil-is-all @JacobSzwejbka @angelayi

A float64 program with x * 0.1 + 0.3 then / 0.7 must give the same result as eager after ScalarToTensorPass and RemoveMixedTypeOperators. Today the scalars are rounded to float32 first. See pytorch#24167.
torch.tensor(value) makes a float32 constant, so a float scalar used with a float64 tensor was rounded to float32 before RemoveMixedTypeOperators cast it back to float64. When the op has a float64 tensor operand, create the constant as float64. Other dtypes are unchanged.

Fixes pytorch#24167
@pytorch-bot

pytorch-bot Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24168

Note: Links to docs will display an error until the docs builds have been completed.

⏳ No Failures, 1 Pending

As of commit 2eac08c with merge base 5b14a2f (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 27, 2026
@executorch-triage executorch-triage Bot added the community: contribution PRs coming from community (excluding hardware partners) label Oct 27, 2026
@nil-is-all nil-is-all added module: exir Issues related to Export IR and the code under exir/ release notes: exir Changes to any dialects and passes on these dialects, such as memory planning labels Oct 27, 2026
if isinstance(value, float) and has_float64_tensor
else None
)
return torch.tensor(value, dtype=dtype)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Float scalars in python are always float64 no? Is the better fix just to always cast them to float64 tensors regardless of if there is another in the op

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Python floats are float64, but the scalar's dtype decides the result dtype when the other operand is an integer tensor. With the scalar as a 0-dim float64 tensor, int32_tensor * 0.1 returns float64, while eager (and this PR) return float32. I tried always using float64 on x * 0.1, x + 0.1, 0.1 - x and x / 0.7, lowered with to_edge(...).to_executorch() and run through portable:

input always float64 this PR
float64 exact exact
float32, float16 unchanged unchanged
int32 float64 output, eager is float32 float32, matches eager

So I'd keep float64 only when the op already has a float64 tensor operand. The other way would be to pick the dtype from meta["val"] of the node's output, if you'd prefer that.

@nil-is-all nil-is-all added need-user-input The issue needs more information from the reporter before moving forward and removed need-user-input The issue needs more information from the reporter before moving forward labels Oct 28, 2026
@JacobSzwejbka
JacobSzwejbka merged commit 254635c into pytorch:main Oct 29, 2026
242 of 250 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. community: contribution PRs coming from community (excluding hardware partners) module: exir Issues related to Export IR and the code under exir/ release notes: exir Changes to any dialects and passes on these dialects, such as memory planning

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Float scalars in float64 programs are rounded to float32 by ScalarToTensorPass

3 participants