Skip to content

Commit 4eda94e

Browse files
Arm backend: Add TOSA dialect conversion ops (#20786)
Add fake implementations for CAST and move RESCALE and CAST_TO_BLOCK_SCALED into conversion_ops.py. cc @digantdesai @freddan80 @per @zingo @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Oscar Andersson <oscar.andersson@arm.com>
1 parent d065779 commit 4eda94e

10 files changed

Lines changed: 332 additions & 253 deletions

File tree

backends/arm/operator_support/TARGETS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ runtime.python_library(
77
"//executorch/backends/arm:ao_ext",
88
"//executorch/backends/arm:constants",
99
"//executorch/backends/arm/_passes:passes",
10+
"//executorch/backends/arm/tosa:cast_support",
1011
"//executorch/backends/arm/tosa:resize_utils",
1112
"//executorch/backends/arm/tosa:tosa",
1213
"//executorch/backends/transforms:remove_getitem_op",

backends/arm/operator_support/to_dim_order_copy_support.py

Lines changed: 8 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
1111
"""
1212

13-
import copy
1413
import logging
1514

1615
import torch
@@ -22,123 +21,23 @@
2221
SupportedTOSAOperatorCheck,
2322
)
2423
from executorch.backends.arm.tosa import TosaSpecification
24+
from executorch.backends.arm.tosa.cast_support import (
25+
supported_to_dim_order_casts,
26+
SupportedCastMap,
27+
)
2528
from executorch.exir.dialects._ops import ops as exir_ops
2629

2730
logger = logging.getLogger(__name__)
2831

29-
SupportedTypeDict = dict[torch.dtype, list[torch.dtype]]
30-
3132

3233
@register_tosa_support_check
3334
class ToCopySupported(SupportedTOSAOperatorCheck):
34-
"""Provide TOSA support check for ``_to_dim_order_copy``.
35-
36-
Attributes:
37-
SUPPORTED_INT_PROFILE_DTYPES (dict[torch.dtype, list[torch.dtype]]):
38-
Allowed output dtypes for each integer input dtype.
39-
SUPPORTED_FP_PROFILE_DTYPES (dict[torch.dtype, list[torch.dtype]]):
40-
Allowed output dtypes for each floating input dtype.
41-
42-
"""
35+
"""Provide TOSA support check for ``_to_dim_order_copy``."""
4336

4437
targets = [
4538
exir_ops.edge.dim_order_ops._to_dim_order_copy.default,
4639
]
4740

48-
@staticmethod
49-
def _merge_supported_types(
50-
dtypes1: SupportedTypeDict,
51-
dtypes2: SupportedTypeDict,
52-
) -> SupportedTypeDict:
53-
"""Return a merged mapping of supported dtype transitions.
54-
55-
Args:
56-
dtypes1 (dict[torch.dtype, list[torch.dtype]]): Base mapping.
57-
dtypes2 (dict[torch.dtype, list[torch.dtype]]): Mapping to merge in.
58-
59-
Returns:
60-
dict[torch.dtype, list[torch.dtype]]: Combined mapping.
61-
62-
"""
63-
merged_dtypes = copy.deepcopy(
64-
dtypes1
65-
) # Use deepcopy to avoid unintentionally modifying SUPPORTED_INT_PROFILE_DTYPES
66-
for k, v in dtypes2.items():
67-
merged_dtypes[k] = merged_dtypes.get(k, []) + v
68-
return merged_dtypes
69-
70-
SUPPORTED_INT_PROFILE_DTYPES: SupportedTypeDict = {
71-
torch.bool: [torch.bool, torch.int8, torch.int16, torch.int32],
72-
torch.int8: [torch.bool, torch.int8, torch.int16, torch.int32],
73-
torch.int16: [torch.bool, torch.int8, torch.int16, torch.int32],
74-
torch.int32: [torch.bool, torch.int8, torch.int16, torch.int32],
75-
torch.int64: [torch.bool, torch.int8, torch.int16, torch.int32],
76-
}
77-
SUPPORTED_FP_PROFILE_DTYPES: SupportedTypeDict = {
78-
torch.int8: [torch.int8, torch.float16, torch.float32],
79-
torch.int16: [torch.int16, torch.float16, torch.float32],
80-
torch.int32: [torch.int32, torch.float16, torch.float32],
81-
# INT64 inputs to casts *should* be ok, since they should be rejected by
82-
# CheckInt64InputsAndOutputs if the cast can't be done AOT.
83-
torch.int64: [
84-
torch.int8,
85-
torch.int16,
86-
torch.int32,
87-
torch.float16,
88-
torch.float32,
89-
],
90-
torch.float16: [
91-
torch.int8,
92-
torch.int16,
93-
torch.int32,
94-
torch.float16,
95-
torch.float32,
96-
],
97-
torch.float32: [
98-
torch.float32,
99-
torch.int8,
100-
torch.int16,
101-
torch.int32,
102-
torch.float16,
103-
torch.float32,
104-
],
105-
}
106-
SUPPORTED_INT_FP_PROFILE_DTYPES: SupportedTypeDict = {
107-
torch.bool: [torch.float32],
108-
}
109-
SUPPORTED_BF16_EXTENSION_DTYPES: SupportedTypeDict = {
110-
torch.int8: [torch.bfloat16],
111-
torch.int16: [torch.bfloat16],
112-
torch.int32: [torch.bfloat16],
113-
torch.int64: [torch.bfloat16],
114-
torch.float32: [torch.bfloat16],
115-
torch.bfloat16: [
116-
torch.int8,
117-
torch.int16,
118-
torch.int32,
119-
torch.bfloat16,
120-
torch.float32,
121-
],
122-
}
123-
SUPPORTED_FP8E4M3_EXTENSION_DTYPES: SupportedTypeDict = {
124-
torch.float16: [torch.float8_e4m3fn],
125-
torch.float32: [torch.float8_e4m3fn],
126-
torch.float8_e4m3fn: [torch.float16, torch.float32],
127-
}
128-
SUPPORTED_FP8E5M2_EXTENSION_DTYPES: SupportedTypeDict = {
129-
torch.float16: [torch.float8_e5m2],
130-
torch.float32: [torch.float8_e5m2],
131-
torch.float8_e5m2: [torch.float16, torch.float32],
132-
}
133-
SUPPORTED_BF16_FP8E4M3_EXTENSION_DTYPES: SupportedTypeDict = {
134-
torch.bfloat16: [torch.float8_e4m3fn],
135-
torch.float8_e4m3fn: [torch.bfloat16],
136-
}
137-
SUPPORTED_BF16_FP8E5M2_EXTENSION_DTYPES: SupportedTypeDict = {
138-
torch.bfloat16: [torch.float8_e5m2],
139-
torch.float8_e5m2: [torch.bfloat16],
140-
}
141-
14241
@staticmethod
14342
def _is_quantized_identity_cast(node: torch.fx.Node) -> bool:
14443
for user in node.users:
@@ -153,7 +52,7 @@ def _is_quantized_identity_cast(node: torch.fx.Node) -> bool:
15352
return False
15453
return True
15554

156-
def is_node_tosa_supported( # noqa: C901
55+
def is_node_tosa_supported(
15756
self, node: fx.Node, tosa_spec: TosaSpecification
15857
) -> bool:
15958
"""Return True if the node is supported by TOSA.
@@ -163,43 +62,7 @@ def is_node_tosa_supported( # noqa: C901
16362
input dtype.
16463
16564
"""
166-
supported_dtypes: SupportedTypeDict = {}
167-
if tosa_spec.support_integer():
168-
supported_dtypes = self._merge_supported_types(
169-
self.SUPPORTED_INT_PROFILE_DTYPES, supported_dtypes
170-
)
171-
if tosa_spec.support_float():
172-
supported_dtypes = self._merge_supported_types(
173-
self.SUPPORTED_FP_PROFILE_DTYPES, supported_dtypes
174-
)
175-
if tosa_spec.support_integer() and tosa_spec.support_float():
176-
supported_dtypes = self._merge_supported_types(
177-
self.SUPPORTED_INT_FP_PROFILE_DTYPES, supported_dtypes
178-
)
179-
if tosa_spec.support_extension("bf16"):
180-
supported_dtypes = self._merge_supported_types(
181-
self.SUPPORTED_BF16_EXTENSION_DTYPES, supported_dtypes
182-
)
183-
if tosa_spec.support_extension("fp8e4m3"):
184-
supported_dtypes = self._merge_supported_types(
185-
self.SUPPORTED_FP8E4M3_EXTENSION_DTYPES, supported_dtypes
186-
)
187-
if tosa_spec.support_extension("fp8e5m2"):
188-
supported_dtypes = self._merge_supported_types(
189-
self.SUPPORTED_FP8E5M2_EXTENSION_DTYPES, supported_dtypes
190-
)
191-
if tosa_spec.support_extension("bf16") and tosa_spec.support_extension(
192-
"fp8e4m3"
193-
):
194-
supported_dtypes = self._merge_supported_types(
195-
self.SUPPORTED_BF16_FP8E4M3_EXTENSION_DTYPES, supported_dtypes
196-
)
197-
if tosa_spec.support_extension("bf16") and tosa_spec.support_extension(
198-
"fp8e5m2"
199-
):
200-
supported_dtypes = self._merge_supported_types(
201-
self.SUPPORTED_BF16_FP8E5M2_EXTENSION_DTYPES, supported_dtypes
202-
)
65+
supported_dtypes: SupportedCastMap = supported_to_dim_order_casts(tosa_spec)
20366

20467
if len(node.all_input_nodes) != 1:
20568
self.reporter.report_reject(
@@ -221,7 +84,6 @@ def is_node_tosa_supported( # noqa: C901
22184
)
22285
return False
22386

224-
# Check input type
22587
input_dtype = input_val.dtype
22688
if input_dtype not in supported_dtypes:
22789
self.reporter.report_reject(
@@ -230,7 +92,6 @@ def is_node_tosa_supported( # noqa: C901
23092
)
23193
return False
23294

233-
# Check output type
23495
output_val = node.meta["val"]
23596
if not isinstance(output_val, torch._subclasses.FakeTensor):
23697
self.reporter.report_reject(
@@ -242,10 +103,7 @@ def is_node_tosa_supported( # noqa: C901
242103
)
243104
return False
244105
if output_val.dtype not in supported_dtypes[input_dtype]:
245-
if (
246-
tosa_spec.support_integer()
247-
and ToCopySupported._is_quantized_identity_cast(node)
248-
):
106+
if tosa_spec.support_integer() and self._is_quantized_identity_cast(node):
249107
return True
250108
self.reporter.report_reject(
251109
node,

backends/arm/test/misc/tosa_dialect/test_tosa_dialect_cast_to_block_scaled.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import torch
88
from executorch.backends.arm.ao_ext.mxfp import mxfp_dtype_to_str
99
from executorch.backends.arm.tosa.dialect.lib import TosaValueError
10-
from executorch.backends.arm.tosa.dialect.ops import cast_to_block_scaled # noqa: F401
1110
from executorch.backends.arm.tosa.specification import (
1211
TosaLoweringContext,
1312
TosaSpecification,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2026 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
import executorch.backends.arm.tosa.dialect # noqa: F401
7+
import pytest
8+
import torch
9+
from executorch.backends.arm.tosa.dialect.lib import TosaValueError
10+
from executorch.backends.arm.tosa.specification import (
11+
TosaLoweringContext,
12+
TosaSpecification,
13+
)
14+
from executorch.exir.dialects._ops import ops as exir_ops
15+
from torch._subclasses.fake_tensor import FakeTensorMode
16+
17+
18+
def test_cast_tosa_int() -> None:
19+
tosa_spec = TosaSpecification.create_from_string("TOSA-1.0+INT")
20+
21+
with TosaLoweringContext(tosa_spec), FakeTensorMode() as mode:
22+
input_tensor = mode.from_tensor(torch.ones((2, 3), dtype=torch.int8))
23+
output = exir_ops.backend.tosa.CAST.default(input_tensor, torch.int32)
24+
25+
assert output.dtype == torch.int32
26+
assert tuple(output.shape) == (2, 3)
27+
28+
29+
def test_cast_rejects_unsupported_profile_dtype_pair() -> None:
30+
tosa_spec = TosaSpecification.create_from_string("TOSA-1.0+INT")
31+
32+
with TosaLoweringContext(tosa_spec), FakeTensorMode() as mode:
33+
input_tensor = mode.from_tensor(torch.ones((2, 3), dtype=torch.int8))
34+
with pytest.raises(TosaValueError, match="Unsupported CAST"):
35+
exir_ops.backend.tosa.CAST.default(input_tensor, torch.float32)

backends/arm/tosa/BUCK

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ fbcode_target(_kind = runtime.python_library,
1010
visibility = ["PUBLIC"],
1111
)
1212

13+
fbcode_target(_kind = runtime.python_library,
14+
name = "cast_support",
15+
srcs = [
16+
"cast_support.py",
17+
],
18+
deps = [
19+
"//caffe2:torch",
20+
":specification",
21+
],
22+
)
23+
1324
fbcode_target(_kind = runtime.python_library,
1425
name = "mapping",
1526
srcs = [

0 commit comments

Comments
 (0)