Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions backends/arm/_passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@
)
from .normalize_while_initial_args_pass import NormalizeWhileInitialArgsPass # noqa
from .promote_bool_operands_pass import PromoteBoolOperandsPass # noqa
from .propagate_view_copy_permute_pass import ( # noqa
PropagateViewCopyPermuteDownPass,
PropagateViewCopyPermuteUpPass,
)
from .remove_getitem_pass import RemoveGetItemPass # noqa
from .remove_graph_asserts_pass import RemoveGraphAssertsPass # noqa
from .remove_noop_pass import RemoveNoopPass # noqa
Expand Down
14 changes: 6 additions & 8 deletions backends/arm/_passes/arm_pass_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
AccumulateIndexPutPass,
BroadcastArgsPass,
CanonicalizeGatherPass,
CanonicalizeViewCopyPermutePass,
CastInt64BuffersToInt32Pass,
CastToInt32Pass,
ComputeConstantOpsAOTPass,
Expand Down Expand Up @@ -130,11 +129,12 @@
NormalizeIndexPutNoneIndicesPass,
NormalizeWhileInitialArgsPass,
PromoteBoolOperandsPass,
PropagateViewCopyPermuteDownPass,
PropagateViewCopyPermuteUpPass,
QuantizeClampArgumentsPass,
RemoveGetItemPass,
RemoveGraphAssertsPass,
RemoveNoopPass,
RemovePermutesAroundElementwiseTosaOps,
ReplaceInfAndLimitValuesPass,
ReplaceScalarWithTensorByProfilePass,
RewriteAdaptiveAvgPool2dPass,
Expand Down Expand Up @@ -167,9 +167,6 @@
TosaLoweringContext,
TosaSpecification,
)
from executorch.backends.transforms.fuse_cascaded_transpose_or_permute_ops import (
FuseCascadedTransposeOrPermuteOps,
)

from executorch.exir import ExportedProgram
from executorch.exir._program_utils import _get_updated_graph_signature
Expand Down Expand Up @@ -597,6 +594,7 @@ def _tosa_pipeline(
RewriteAvgPool2dPass(),
ComputeConstantOpsAOTPass(exported_program),
FuseConstantArgsPass(exported_program),
CastInt64BuffersToInt32Pass(exported_program),
DecomposeSelectPass(),
ConvertSqueezesToViewPass(),
CastToInt32Pass(),
Expand All @@ -620,13 +618,13 @@ def _tosa_pipeline(
RewriteMatmulPass(),
RewritePadPass(),
FuseViewCopyTransformPass(),
RemovePermutesAroundElementwiseTosaOps(exported_program),
CanonicalizeViewCopyPermutePass(),
FuseCascadedTransposeOrPermuteOps(),
PropagateViewCopyPermuteDownPass(self.compile_spec, exported_program),
PropagateViewCopyPermuteUpPass(self.compile_spec, exported_program),
RewriteHighRankSingletonPermutePass(),
DecomposePermuteForU55Pass(),
RewriteSlicePass(),
InsertConstShapesPass(),
InsertDataLayoutCastsPass(),
]
)

Expand Down
269 changes: 263 additions & 6 deletions backends/arm/_passes/dim_maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,28 @@ def map_dim(
return None

groups = self._valid_groups()
if not self._is_valid_reduction(normalized_dims, groups.source_axis_to_groups):
if not self._is_valid_reduction_or_singleton(
normalized_dims, groups.source_axis_to_groups
):
return None

target_dims = self._map_dims(
normalized_dims,
groups.source_axis_to_groups,
groups.group_to_target_axes,
source_to_target_axes = self.source_to_target_axes()
target_dims = sorted(
_dedupe(
target_axis
for source_dim in normalized_dims
for target_axis in source_to_target_axes[source_dim]
)
)
if not target_dims or not self._is_valid_reduction(
if not target_dims or any(
source_axis not in normalized_dims
for target_axis in target_dims
for source_axis in self.source_axes_for_target_axis(
target_axis, source_to_target_axes
)
):
return None
if not self._is_valid_reduction_or_singleton(
target_dims, groups.target_axis_to_groups
):
return None
Expand Down Expand Up @@ -432,6 +445,226 @@ def map_permutation_inverse(
else None
)

def remap_target_shape(self, source_shape: Sequence[_Dim]) -> list[_Dim] | None:
if len(source_shape) != self.source_rank:
return None

source_to_target_axes = self.source_to_target_axes()
target_to_source_axes = [
self.source_axes_for_target_axis(target_axis, source_to_target_axes)
for target_axis in range(self.target_rank)
]
target_shape: list[_Dim] = [1] * self.target_rank

for source_axis, target_axes in enumerate(source_to_target_axes):
updates = self._target_axis_updates_for_source_axis(
source_shape,
source_axis,
target_axes,
target_to_source_axes,
)
if updates is None:
return None
for target_axis, target_dim in updates:
target_shape[target_axis] = target_dim

if not same_numel(source_shape, target_shape):
return None
if not self._preserves_source_axis_order(source_shape, source_to_target_axes):
return None
return target_shape

def _target_axis_updates_for_source_axis(
self,
source_shape: Sequence[_Dim],
source_axis: int,
target_axes: Sequence[int],
target_to_source_axes: Sequence[Sequence[int]],
) -> list[tuple[int, _Dim]] | None:
if not target_axes:
return []

if len(target_axes) == 1:
target_axis = target_axes[0]
source_axes = target_to_source_axes[target_axis]
if source_axis != source_axes[0]:
return []
target_dim = numel(source_shape[source_axis] for source_axis in source_axes)
return [(target_axis, target_dim)]

if any(
len(target_to_source_axes[target_axis]) > 1 for target_axis in target_axes
):
return []

target_dims = [self.target_shape[target_axis] for target_axis in target_axes]
if _dim_equals(source_shape[source_axis], self.source_shape[source_axis]):
return list(zip(target_axes, target_dims))
if _dim_equals(numel(target_dims), 1):
return [(target_axes[0], source_shape[source_axis])]
if _dim_equals(numel(target_dims), self.source_shape[source_axis]):
return list(zip(target_axes, target_dims))
return None

def remap_unit_slice(
self,
producer_shape: Sequence[_Dim],
slice_dim: int,
start: _Dim,
end: _Dim,
step: _Dim = 1,
) -> tuple[list[_Dim], int, _Dim, _Dim] | None:
"""Move a view before a unit slice.

Returns the new view shape and slice interval for:

view(slice(x, dim, start, end), self.target_shape)
== slice(view(x, new_shape), new_dim, new_start, new_end)

This handles the case where a unit slice produces a singleton source
axis that the view removes, so normal source-to-target dim mapping has
no target axis for the slice dim.

"""
if (
len(producer_shape) != self.source_rank
or not isinstance(slice_dim, int)
or not isinstance(start, (int, torch.SymInt))
or not isinstance(end, (int, torch.SymInt))
or not isinstance(step, (int, torch.SymInt))
):
return None
if not _dim_equals(step, 1) or not _dim_equals(end - start, 1):
return None

try:
slice_dim = _normalize_dim(slice_dim, self.source_rank)
except AssertionError:
return None

source_to_target_axes = self.source_to_target_axes()
if source_to_target_axes[slice_dim]:
return None

prev_target_axes = [
target_axis
for target_axes in source_to_target_axes[:slice_dim]
for target_axis in target_axes
]
next_target_axes = [
target_axis
for target_axes in source_to_target_axes[slice_dim + 1 :]
for target_axis in target_axes
]
fold_axes = [
target_axes[0]
for target_axes in source_to_target_axes[slice_dim + 1 :]
if target_axes
]
fold_axes = [
target_axis
for target_axis in fold_axes
if all(
prev_target_axis <= target_axis for prev_target_axis in prev_target_axes
)
and all(
target_axis <= next_target_axis for next_target_axis in next_target_axes
)
]
if not fold_axes:
return None

fold_axis = fold_axes[0]
target_shape = list(self.target_shape)
chunk = target_shape[fold_axis]
target_shape[fold_axis] = chunk * producer_shape[slice_dim]
return target_shape, fold_axis, start * chunk, end * chunk

def source_to_target_axes(self) -> list[list[int]]:
groups = self._valid_groups()
source_to_target_axes = [
self._map_dims(
[source_axis],
groups.source_axis_to_groups,
groups.group_to_target_axes,
)
for source_axis in range(self.source_rank)
]

self._add_singleton_axes(source_to_target_axes)
return source_to_target_axes

def map_source_dims_to_target_axes(
self, source_dims: int | Sequence[int]
) -> list[int] | None:
try:
normalized_dims = _normalize_dims(source_dims, self.source_rank)
except AssertionError:
return None
source_to_target_axes = self.source_to_target_axes()
return _dedupe(
target_axis
for source_dim in normalized_dims
for target_axis in source_to_target_axes[source_dim]
)

@staticmethod
def source_axes_for_target_axis(
target_axis: int, source_to_target_axes: Sequence[Sequence[int]]
) -> list[int]:
return [
source_axis
for source_axis, target_axes in enumerate(source_to_target_axes)
if target_axis in target_axes
]

def _add_singleton_axes(self, source_to_target_axes: list[list[int]]) -> None:
mapped_source_axes = {
source_axis
for source_axis, target_axes in enumerate(source_to_target_axes)
if target_axes
}
mapped_target_axes = {
target_axis
for target_axes in source_to_target_axes
for target_axis in target_axes
}
source_singletons = [
axis
for axis, dim in enumerate(self.source_shape)
if axis not in mapped_source_axes and _dim_equals(dim, 1)
]
target_singletons = [
axis
for axis, dim in enumerate(self.target_shape)
if axis not in mapped_target_axes and _dim_equals(dim, 1)
]

if len(source_singletons) == len(target_singletons):
pairs = zip(source_singletons, target_singletons)
elif len(source_singletons) == 1:
pairs = zip(source_singletons * len(target_singletons), target_singletons)
elif len(target_singletons) == 1:
pairs = zip(source_singletons, target_singletons * len(source_singletons))
else:
pairs = zip(source_singletons, target_singletons)

for source_axis, target_axis in pairs:
source_to_target_axes[source_axis].append(target_axis)

@staticmethod
def _preserves_source_axis_order(
source_shape: Sequence[_Dim],
source_to_target_axes: Sequence[Sequence[int]],
) -> bool:
target_axes = [
target_axis
for source_axis, axes in enumerate(source_to_target_axes)
if not _dim_equals(source_shape[source_axis], 1)
for target_axis in axes
]
return target_axes == sorted(target_axes)

@staticmethod
def _map_dims(
source_dims: Iterable[int],
Expand Down Expand Up @@ -553,6 +786,30 @@ def _is_valid_reduction(
group_to_axes[group].issubset(normalized_dims) for group in selected_groups
)

@staticmethod
def _is_valid_reduction_or_singleton(
normalized_dims: Iterable[int],
axis_to_groups: Sequence[Sequence[int]],
) -> bool:
"""Return whether dims cover complete groups, allowing singleton
axes.
"""
normalized_dims = set(normalized_dims)
if not normalized_dims:
return False

group_to_axes: dict[int, set[int]] = defaultdict(set)
selected_groups: set[int] = set()
for axis, groups in enumerate(axis_to_groups):
for group in groups:
group_to_axes[group].add(axis)
if axis in normalized_dims:
selected_groups.add(group)

return all(
group_to_axes[group].issubset(normalized_dims) for group in selected_groups
)

@classmethod
def _build_groups(
cls, source_shape: Sequence[_Dim], target_shape: Sequence[_Dim]
Expand Down
7 changes: 7 additions & 0 deletions backends/arm/_passes/insert_data_layout_casts_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class InsertDataLayoutCastsPass(ArmOpTargetedPass):
_concat_ops = {
exir_ops.edge.aten.cat.default,
exir_ops.edge.aten.concatenate.default,
exir_ops.backend.tosa.CONCAT.default,
}
_single_input_ops = {
exir_ops.edge.aten.constant_pad_nd.default,
Expand All @@ -44,6 +45,12 @@ class InsertDataLayoutCastsPass(ArmOpTargetedPass):
exir_ops.edge.aten.permute_copy.default,
exir_ops.edge.aten.slice_copy.Tensor,
exir_ops.edge.aten.flip.default,
exir_ops.backend.tosa.PAD.default,
exir_ops.backend.tosa.RESHAPE.default,
exir_ops.backend.tosa.TILE.default,
exir_ops.backend.tosa.TRANSPOSE.default,
exir_ops.backend.tosa.SLICE.default,
exir_ops.backend.tosa.REVERSE.default,
}
target_ops = _concat_ops | _single_input_ops

Expand Down
Loading
Loading