Skip to content

Add sym_float and trunc support for dynamic float-scale interpolate - #2843

Open
SakshamKapoor2911 wants to merge 2 commits into
apple:mainfrom
SakshamKapoor2911:fix/pr2837-dynamic-float-scale
Open

SakshamKapoor2911 wants to merge 2 commits into
apple:mainfrom
SakshamKapoor2911:fix/pr2837-dynamic-float-scale

Conversation

@SakshamKapoor2911

Copy link
Copy Markdown
Contributor

Summary

  • Add sym_float and trunc PyTorch frontend lowerings so that nn.functional.interpolate(..., scale_factor=<float>, recompute_scale_factor=True) converts over dynamic (symbolic) input shapes.
  • torch.export decomposes the output-size computation into sym_float -> mul -> trunc FX nodes, which previously failed with NotImplementedError: Unsupported fx node sym_float.
  • sym_float lowers a symbolic integer dimension (from aten.sym_size) to fp32; since it is a runtime dimension, it stays dynamic.
  • trunc lowers to sign(x) * floor(|x|) (the same building blocks as the existing frac lowering) and is the identity on integer tensors; this also enables torch.trunc in the converter generally.
  • Cast the dynamic output sizes to int32 in upsample_nearest1d / upsample_nearest2d, mirroring what upsample_bilinear2d already does, so the Torch upsample dialect ops receive int32 output sizes.
  • Teach the torch_upsample_to_core_upsample SSA pass to recover the constant float scale_factor from the new cast(int32) -> trunc -> floor -> abs -> mul chain, and only fill in scale factors that earlier patterns could not resolve, so a previously resolved value (e.g. the constant dummy width of a 1d upsample) is not overwritten.
  • Add dynamic-shape tests for float-scale nearest and bilinear 2D upsampling.

Related to #2837 (symbolic F.interpolate). This PR handles the float scale_factor decomposition; the 1D symbolic-output-size rank fix is covered by #2840.

Testing

Local (Linux) conversion/structure tests, plus numerically-executed MIL parity:

python -m pytest coremltools/converters/mil/frontend/torch/test/test_torch_ops.py -k "interpolate" -q

Result: 4 passed, 4 xfailed (XFAILs are the pre-existing Executorch dynamic-shape limitation). Full TestUpsample: 296 passed, 26 xfailed — no regressions.

Numerical parity against PyTorch (fp32, converted MIL executed op-by-op): max diff 0.0 for float scale_factor 2.0 and (2.5, 1.5) over symbolic H/W, plus static-shape regressions.

"""
try:
return export_fn()
except Exception as e: # noqa: BLE001

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.

You don't need the noqa comment.

self, compute_unit, backend, frontend
):
if frontend == TorchFrontend.EXECUTORCH:
pytest.xfail("executorch incorrectly propagates dynamic shape")

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.

How is it incorrect? Is there a PyTorch issue for this?

msg = str(e)
if "trunc" in msg or "SpecViolationError" in msg:
pytest.xfail(
"torch.export verifier rejects the trunc node on this torch version"

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.

Why does it reject some of these models?

assert len(layer.upsample.fractionalScalingFactor) == 0

@staticmethod
def _xfail_if_torch_export_rejects_trunc(export_fn):

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.

I think it would be much cleaner to only test valid use cases and not use this helper method.

Remove the _xfail_if_torch_export_rejects_trunc helper: current torch
export verifier accepts the sym_float/mul/trunc decomposition, so the
defensive xfail path is dead code. Call run_compare_torch directly.

Drop ExecuTorch from the parametrization: dynamic-shape interpolate
with symbolic output sizes is not supported by the ExecuTorch edge
lowering (upper-bound/padded dynamic shapes only), so these tests now
only exercise the TorchExport frontend, which is the valid use case.
@SakshamKapoor2911

SakshamKapoor2911 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@TobyRoseman Thanks for the review. I've refactored the two dynamic float-scale tests in test_torch_ops.py per your comments, and pushed the change as 0dc5d1d6:

Removed the _xfail_if_torch_export_rejects_trunc helper. On current torch versions the torch.export verifier accepts the sym_float/trunc decomposition that F.interpolate(..., scale_factor=<float>, recompute_scale_factor=True) produces over dynamic shapes (I verified the exported graph on torch 2.7 contains sym_float -> mul -> math_trunc and converts cleanly), so the helper's xfail branch was dead code. Both tests now call run_compare_torch directly, and the # noqa comment is gone with the helper. The SpecViolationError the helper guarded against was real for older torch (it's the same reason the pre-existing test_upsample_linear1d_with_scales_dynamic carries a static xfail for Operator '<built-in function trunc>' is not an allowed operator), but it no longer applies to supported torch versions.

Dropped the ExecuTorch frontend from these two tests (they now parametrize only TorchFrontend.TORCHEXPORT) and removed the pytest.xfail("executorch incorrectly propagates dynamic shape") branch. To answer your question: ExecuTorch's dynamic-shape support is built around upper-bounded/static shapes rather than evaluating symbolic runtime dimension expressions into new output sizes, so a symbolic output-size decomposition is not a valid ExecuTorch use case for interpolate. Symbol manipulation under dynamic shapes has known upstream gaps (pytorch/executorch#4658, #20712) and there have been output-shape correctness bugs under dynamic shapes (pytorch/executorch#21376). The same xfail is used by the pre-existing dynamic upsample tests in this file; per your suggestion I've limited these tests to the frontend where the path is actually exercised instead of xfailing.

Local verification: TestUpsample 296 passed / 22 xfailed (four fewer xfails than before, no regressions) and flake8 E9,F5,F63,F7,F82 clean. Could you take another look?

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.

2 participants