Skip to content

Commit da547e0

Browse files
Arm backend: Add tosa-dialect op for SLICE (pytorch#18093)
For the upcoming shape-extension, convolution will insert slices with symints are arguments. To make this work we need to move logic out of the slice-visitor and into a pass that rewrites aten.slice.Tensor to a tosa.SLICE. The biggest difference is that tosa.SLICE operates on lists as start and size arguments. This simplifies the conversion of symbolic expressions to tosa-shape operations. cc @digantdesai @freddan80 @per @zingo @mansnils @Sebastian-Larsson @robell Signed-off-by: Oscar Andersson <oscar.andersson@arm.com> Co-authored-by: Per Åstrand <per.astrand@arm.com>
1 parent 0cddbc7 commit da547e0

8 files changed

Lines changed: 184 additions & 153 deletions

File tree

‎backends/arm/_passes/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
from .rewrite_le_lt_to_ge_gt_pass import RewriteLeLtToGeGtPass # noqa
137137
from .rewrite_matmul import RewriteMatmulPass # noqa
138138
from .rewrite_pad import RewritePadPass # noqa
139+
from .rewrite_slice import RewriteSlicePass # noqa
139140
from .rewrite_upsample import RewriteUpsamplePass # noqa
140141
from .scalars_to_attribute_pass import ScalarsToAttributePass # noqa
141142
from .size_adjust_input_pass import SizeAdjustInputPass # noqa

‎backends/arm/_passes/arm_pass_manager.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
RewriteLeLtToGeGtPass,
122122
RewriteMatmulPass,
123123
RewritePadPass,
124+
RewriteSlicePass,
124125
RewriteUpsamplePass,
125126
ScalarsToAttributePass,
126127
SizeAdjustInputPass,
@@ -374,6 +375,7 @@ def _tosa_pipeline(
374375
RewriteConvPass(exported_program),
375376
RewriteMatmulPass(),
376377
RewritePadPass(),
378+
RewriteSlicePass(),
377379
]
378380
)
379381

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
from typing import Set, Type
6+
7+
from executorch.backends.arm._passes import ArmPass
8+
9+
from executorch.exir.dialects._ops import ops as exir_ops
10+
from executorch.exir.pass_base import ExportPass, ProxyValue
11+
12+
from torch import SymInt
13+
14+
15+
class RewriteSlicePass(ArmPass):
16+
"""Rewrite slice operations with step of 1 to TOSA slice operators."""
17+
18+
_passes_required_after: Set[Type[ExportPass]] = set()
19+
20+
def _fixup_start(self, start, input_shape, dim) -> int:
21+
"""Convert negative and out-of-bounds start indices to valid positive
22+
indices.
23+
"""
24+
idx = start or 0
25+
if idx < 0:
26+
idx = start % input_shape[dim]
27+
if idx >= input_shape[dim]:
28+
idx = input_shape[dim] - 1
29+
return idx
30+
31+
def call_operator(self, op, args, kwargs, meta, updated=False) -> ProxyValue:
32+
if op not in (exir_ops.edge.aten.slice_copy.Tensor,):
33+
return super().call_operator(op, args, kwargs, meta, updated)
34+
35+
if len(args) == 5 and args[4] != 1:
36+
raise ValueError(
37+
f"Only slice with 4 arguments and step of 1 is supported, got {len(args)} arguments and step {args[4]}"
38+
)
39+
input, dim, start, end = args[:4]
40+
input_shape = input.data.shape
41+
start_index = self._fixup_start(start, input_shape, dim)
42+
43+
start_list = [0] * len(input_shape)
44+
start_list[dim] = start_index
45+
size_list = list(meta.data["val"].shape)
46+
47+
if any(isinstance(dim, SymInt) for dim in start_list):
48+
starts = start_list
49+
else:
50+
starts = super().call_shape_operator( # type: ignore[assignment]
51+
exir_ops.backend.tosa.CONST_SHAPE.default,
52+
(start_list,),
53+
{},
54+
meta,
55+
True,
56+
)
57+
if any(isinstance(dim, SymInt) for dim in size_list):
58+
sizes = size_list
59+
else:
60+
sizes = super().call_shape_operator( # type: ignore[assignment]
61+
exir_ops.backend.tosa.CONST_SHAPE.default,
62+
(size_list,),
63+
{},
64+
meta,
65+
True,
66+
)
67+
68+
return super().call_operator(
69+
exir_ops.backend.tosa.SLICE.default,
70+
(input, starts, sizes),
71+
kwargs,
72+
meta,
73+
updated=True,
74+
)

‎backends/arm/operators/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
op_rsqrt,
4747
op_sigmoid,
4848
op_sin,
49-
op_slice,
5049
op_sub,
5150
op_sum,
5251
op_tanh,
@@ -61,6 +60,7 @@
6160
op_tosa_resize,
6261
op_tosa_scatter,
6362
op_tosa_shapes,
63+
op_tosa_slice,
6464
op_tosa_table,
6565
op_tosa_transpose,
6666
op_tosa_transpose_conv2d,

‎backends/arm/operators/op_slice.py‎

Lines changed: 0 additions & 152 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
7+
from typing import Any, List
8+
9+
import tosa_serializer as ts
10+
11+
from executorch.backends.arm.operators.node_visitor import (
12+
NodeVisitor,
13+
register_node_visitor,
14+
)
15+
from executorch.backends.arm.tosa.mapping import TosaArg
16+
from torch.fx import Node
17+
18+
19+
@register_node_visitor
20+
class SliceVisitor(NodeVisitor):
21+
target = "tosa.SLICE.default"
22+
23+
def __init__(self, *args):
24+
super().__init__(*args)
25+
26+
def define_node(
27+
self,
28+
node: Node,
29+
tosa_graph: Any,
30+
inputs: List[TosaArg],
31+
output: TosaArg,
32+
) -> None:
33+
34+
input_node, starts, sizes = inputs
35+
attr = ts.TosaSerializerAttribute()
36+
attr.SliceAttribute()
37+
self._serialize_operator(
38+
node,
39+
tosa_graph,
40+
ts.Op.SLICE,
41+
[input_node.name, starts.name, sizes.name],
42+
[output.name],
43+
attr,
44+
)

‎backends/arm/tosa/dialect/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
resize,
1515
scatter,
1616
shape_ops,
17+
slice,
1718
table,
1819
transpose,
1920
transpose_conv2d,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 torch
7+
from executorch.backends.arm.tosa.dialect.lib import TosaValueError
8+
from executorch.backends.arm.tosa.dialect.ops_registration import register_fake_tosa_op
9+
10+
from executorch.backends.arm.tosa.specification import (
11+
get_context_spec,
12+
TosaSpecification,
13+
)
14+
15+
16+
@register_fake_tosa_op(
17+
"SLICE(Tensor input1, SymInt[] start, SymInt[] size) -> Tensor", # schema
18+
TosaSpecification.all_versions_and_profiles(), # target TOSA specifications
19+
)
20+
def SLICE(a, start, size):
21+
tosa_spec = get_context_spec()
22+
23+
# Rank validation
24+
input_rank = a.dim()
25+
if input_rank != len(start):
26+
raise TosaValueError(
27+
f"start list does not have the same rank {len(start)} as input {input_rank}"
28+
)
29+
if len(start) != len(size):
30+
raise TosaValueError(
31+
f"size list does not have the same rank {len(size)} as start list {len(start)}"
32+
)
33+
34+
# Shape validation
35+
for i in range(len(start)):
36+
dim_start = start[i]
37+
if dim_start < 0 or dim_start > a.shape[i]:
38+
raise TosaValueError(
39+
f"Expected start values between [0, {a.shape[i]}] but got {dim_start}"
40+
)
41+
dim_size = size[i]
42+
if dim_size < 0 or dim_start + dim_size > a.shape[i]:
43+
raise TosaValueError(
44+
f"Expected start + size values between [0, {a.shape[i]}] but got {dim_start + dim_size}"
45+
)
46+
47+
# Dtype validation
48+
supported_dtypes = [torch.bool]
49+
if tosa_spec.support_integer():
50+
supported_dtypes += [torch.int8, torch.int16, torch.int32]
51+
if tosa_spec.support_float():
52+
supported_dtypes += [torch.float16, torch.float32]
53+
if tosa_spec.support_extension("bf16"):
54+
supported_dtypes += [torch.bfloat16]
55+
56+
if a.dtype not in supported_dtypes:
57+
raise TosaValueError(
58+
f"Unsupported dtype {a.dtype} for SLICE. Supported dtypes are {supported_dtypes}"
59+
)
60+
61+
return torch.empty(size=size, dtype=a.dtype)

0 commit comments

Comments
 (0)