Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backends/arm/operator_support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@
to_dim_order_copy_support,
tosa_supported_operators,
unfold_copy_support,
upsample_support,
where_support,
)
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@
exir_ops.edge.aten.select_copy.int,
exir_ops.edge.aten.sub.Tensor,
exir_ops.edge.aten.tanh.default,
exir_ops.edge.aten.upsample_bilinear2d.vec,
exir_ops.edge.aten.upsample_nearest2d.vec,
exir_ops.edge.aten.view_copy.default,
exir_ops.edge.aten.unsqueeze_copy.default,
exir_ops.edge.aten.squeeze_copy.dims,
Expand Down Expand Up @@ -211,8 +209,6 @@
exir_ops.edge.aten._log_softmax.default,
exir_ops.edge.aten.sub.Tensor,
exir_ops.edge.aten.tanh.default,
exir_ops.edge.aten.upsample_bilinear2d.vec,
exir_ops.edge.aten.upsample_nearest2d.vec,
exir_ops.edge.aten.var.correction,
exir_ops.edge.aten.var.dim,
exir_ops.edge.aten.view_copy.default,
Expand Down
69 changes: 69 additions & 0 deletions backends/arm/operator_support/upsample_support.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Copyright 2026 Arm Limited and/or its affiliates.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
"""Provide TOSA support checks for upsample operators."""

import torch.fx as fx
from executorch.backends.arm._passes.arm_pass_utils import get_first_fake_tensor
from executorch.backends.arm._passes.rewrite_upsample import RewriteUpsamplePass
from executorch.backends.arm.common.type import ensure_type
from executorch.backends.arm.operator_support.tosa_supported_operators import (
register_tosa_support_check,
SupportedTOSAOperatorCheck,
)
from executorch.backends.arm.tosa import TosaSpecification
from executorch.exir.dialects._ops import ops as exir_ops


@register_tosa_support_check
class UpsampleNearest2dSupported(SupportedTOSAOperatorCheck):
"""Provide the explicit TOSA support gate for nearest upsample."""

targets = [exir_ops.edge.aten.upsample_nearest2d.vec]

def is_node_tosa_supported(
self, _node: fx.Node, _tosa_spec: TosaSpecification
) -> bool: # type: ignore[override, misc]
return True


@register_tosa_support_check
class UpsampleBilinear2dSupported(SupportedTOSAOperatorCheck):
"""Reject bilinear upsample cases that cannot lower to a valid TOSA
RESIZE.
"""

targets = [exir_ops.edge.aten.upsample_bilinear2d.vec]

def is_node_tosa_supported(
self, node: fx.Node, _tosa_spec: TosaSpecification
) -> bool: # type: ignore[override, misc]
input_node = ensure_type(fx.Node, node.args[0])
align_corners = ensure_type(bool, node.args[2])
input_size_yx = get_first_fake_tensor(input_node).shape[2:]
output_size_yx = get_first_fake_tensor(node).shape[2:]

try:
scale_y_n, scale_y_d, _, _ = RewriteUpsamplePass.get_resize_parameters_1d(
input_size_yx[0], output_size_yx[0], align_corners
)
scale_x_n, scale_x_d, _, _ = RewriteUpsamplePass.get_resize_parameters_1d(
input_size_yx[1], output_size_yx[1], align_corners
)
except RuntimeError as err:
self.reporter.report_reject(node, str(err))
return False

# get_resize_parameters_1d() returns the TOSA RESIZE scale fraction for
# each spatial dimension. For align_corners=False, this is the effective
# output_size / input_size ratio, so the 1/16 boundary is checked
# directly in the same representation that RESIZE lowering will use.
if scale_y_d >= 16 * scale_y_n or scale_x_d >= 16 * scale_x_n:
self.reporter.report_reject(
node,
"Bilinear RESIZE downscale must be strictly greater than 1/16",
)
return False

return True
36 changes: 36 additions & 0 deletions backends/arm/test/misc/test_tosa_dialect_resize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2026 Arm Limited and/or its affiliates.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

import executorch.backends.arm.tosa.dialect # noqa: F401

import pytest
import torch

from executorch.backends.arm.tosa.dialect.lib import TosaValueError
from executorch.backends.arm.tosa.specification import (
TosaLoweringContext,
TosaSpecification,
)
from executorch.exir.dialects._ops import ops as exir_ops
from torch._subclasses.fake_tensor import FakeTensorMode


def test_bilinear_resize_rejects_exact_one_sixteenth_downscale():
with TosaLoweringContext(
TosaSpecification.create_from_string("TOSA-1.0+INT")
), FakeTensorMode() as mode:
with pytest.raises(
TosaValueError,
match="Bilinear RESIZE downscale must be strictly greater than 1/16",
):
exir_ops.backend.tosa.RESIZE.default(
mode.from_tensor(
torch.randint(0, 10, (1, 3, 256, 448), dtype=torch.int8)
),
[2, 32, 2, 32],
[15, 15],
[-15, -15],
resize_mode="bilinear",
)
30 changes: 30 additions & 0 deletions backends/arm/test/ops/test_upsample_bilinear2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,25 @@ def forward(self, x):
return self.upsample(x)


class InterpolateAlignCornersFalse(torch.nn.Module):
def __init__(
self,
size: Optional[Tuple[int]],
scale_factor: Optional[float | Tuple[float]],
):
super().__init__()
self.upsample = lambda x: torch.nn.functional.interpolate(
x,
size=size,
scale_factor=scale_factor,
mode="bilinear",
align_corners=False,
)

def forward(self, x):
return self.upsample(x)


@common.parametrize(
"test_data",
test_data_suite_tosa | test_data_suite_tosa_bf16 | test_data_suite_tosa_fp16,
Expand Down Expand Up @@ -231,6 +250,17 @@ def test_upsample_bilinear2d_vec_tosa_FP_Interpolate(
pipeline.run()


def test_upsample_bilinear2d_vec_tosa_does_not_delegate_exact_one_sixteenth_downscale():
pipeline = OpNotSupportedPipeline[input_t1](
InterpolateAlignCornersFalse(size=None, scale_factor=1.0 / 16.0),
(torch.randn(1, 3, 256, 448),),
{exir_op: 1},
n_expected_delegates=0,
)

pipeline.run()


@common.parametrize("test_data", test_data_suite_tosa)
def test_upsample_bilinear2d_vec_tosa_INT_intropolate(
test_data: torch.Tensor,
Expand Down
1 change: 1 addition & 0 deletions backends/arm/test/targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def define_arm_tests():
"misc/test_compile_spec.py",
# "misc/test_evaluate_model.py",
"misc/test_pass_pipeline_config.py",
"misc/test_tosa_dialect_resize.py",
"misc/test_tosa_spec.py",
"misc/test_bn_relu_folding_qat.py",
"misc/test_custom_partition.py",
Expand Down
11 changes: 9 additions & 2 deletions backends/arm/tosa/dialect/ops/resize.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,21 @@ def _get_output_dtype(
return output_dtype


def _validate_resize_parameters(scale, border):
def _validate_resize_parameters(scale, border, resize_mode):
def in_int16_range(values):
return all((x >= -(2**15)) and (x <= 2**15 - 1) for x in values)

if not in_int16_range(scale):
raise TosaValueError("scale is out of the int16 range", op="RESIZE")
if not in_int16_range(border):
raise TosaValueError("border is out of the int16 range", op="RESIZE")
if resize_mode == "bilinear":
scale_y_n, scale_y_d, scale_x_n, scale_x_d = scale
if scale_y_d >= 16 * scale_y_n or scale_x_d >= 16 * scale_x_n:
raise TosaValueError(
"Bilinear RESIZE downscale must be strictly greater than 1/16",
op="RESIZE",
)


@register_fake_tosa_op(
Expand All @@ -79,7 +86,7 @@ def RESIZE(
f"Input tensor must be 4D, but got {x.dim()}D", op="RESIZE"
)
_validate_resize_mode(resize_mode)
_validate_resize_parameters(scale, border)
_validate_resize_parameters(scale, border, resize_mode)
output_dtype = _get_output_dtype(x.dtype, tosa_spec, resize_mode)

input_shape = x.shape
Expand Down
Loading