Skip to content

Commit 3762019

Browse files
committed
Update
[ghstack-poisoned]
2 parents 820c86f + 9e844e1 commit 3762019

62 files changed

Lines changed: 920 additions & 1841 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/doc-build.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ jobs:
104104
repository: pytorch/executorch
105105
download-artifact: docs
106106
ref: gh-pages
107-
# Publish on the host so actions/checkout's persisted credentials are
108-
# available to git push.
109-
run-with-docker: false
110107
timeout: 90
111108
script: |
112109
set -euo pipefail

.github/workflows/pull.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1427,7 +1427,7 @@ jobs:
14271427
docker-image: ci-image:executorch-ubuntu-22.04-clang12
14281428
submodules: 'recursive'
14291429
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
1430-
timeout: 150
1430+
timeout: 120
14311431
script: |
14321432
set -eux
14331433

backends/mlx/passes.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,49 @@ def call(self, exported_program) -> ExportedProgramPassResult:
9999
}
100100
if ops_to_inplace:
101101
reinplace_pass(exported_program, ops_to_inplace=ops_to_inplace)
102+
self._resync_output_specs(exported_program)
102103
return ExportedProgramPassResult(exported_program, True)
103104

105+
@staticmethod
106+
def _resync_output_specs(exported_program) -> None:
107+
"""Re-sync graph-signature output names after reinplace.
108+
109+
``reinplace_pass`` rewrites an output-producing node (e.g. the final
110+
``exp`` -> ``exp_``) via ``replace_all_uses_with`` + erase, but does not
111+
update ``graph_signature.output_specs``. Output order is preserved, so we
112+
positionally re-sync each spec's argument name to the current output node
113+
arg; otherwise ``ExportedProgram.validate()`` (run by the pass manager)
114+
raises a SpecViolationError.
115+
116+
This positional pairing is only valid because reinplace does a 1:1
117+
``replace_all_uses_with`` + erase and never drops, adds, or reorders
118+
outputs. We assert ``len(output_specs) == len(out_args)`` so that a
119+
future change violating that invariant fails loudly here instead of
120+
silently mis-pairing names (``zip`` would otherwise truncate). Specs
121+
whose ``arg`` is not a named tensor (e.g. ``ConstantArgument``) carry no
122+
``name`` and are skipped by the ``getattr`` guard below.
123+
"""
124+
out_node = next(
125+
n for n in reversed(exported_program.graph.nodes) if n.op == "output"
126+
)
127+
out_args = out_node.args[0]
128+
if not isinstance(out_args, (tuple, list)):
129+
out_args = (out_args,)
130+
output_specs = exported_program.graph_signature.output_specs
131+
assert len(output_specs) == len(out_args), (
132+
"reinplace changed graph output count: "
133+
f"{len(output_specs)} output_specs vs {len(out_args)} output args. "
134+
"Positional output-spec re-sync assumes a 1:1, order-preserving "
135+
"rewrite."
136+
)
137+
for spec, arg in zip(output_specs, out_args):
138+
if (
139+
isinstance(arg, torch.fx.Node)
140+
and getattr(spec.arg, "name", None) is not None
141+
and spec.arg.name != arg.name
142+
):
143+
spec.arg.name = arg.name
144+
104145

105146
@dataclass
106147
class RMSNormMatch(PatternMatch):

backends/nxp/backend/ir/converter/node_converter.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import logging
77
import operator
88
from abc import ABC, abstractmethod
9-
from math import prod
109
from typing import Callable
1110

1211
import torch
@@ -466,30 +465,29 @@ def uses_shape_broadcasting(node: Node) -> bool:
466465
)
467466

468467
@staticmethod
469-
def inputs_satisfy_broadcast_condition(node: Node) -> bool:
470-
"""Determine if given PyTorch fx Node has inputs that satisfy broadcasting conditions for Neutron or not.
468+
def at_least_one_input_shape_matches_the_output_shape(node: Node) -> bool:
469+
"""Determine if given PyTorch fx Node uses at least one input shape broadcasting for it's input nodes or not.
471470
472471
:param node: PyTorch fx Node with 'all_input_nodes' initialized.
473-
:return: True, if at least one input has the same number of elements as the output node.
472+
:return: True, if at least one input has the same shape as the output node.
474473
False otherwise.
475474
"""
476475

477476
if node.all_input_nodes is None:
478477
logger.e(
479478
logger.Code.INTERNAL_ERROR,
480-
"node_converter.inputs_satisfy_broadcast_condition(): 'all_input_nodes' are None!",
479+
"node_converter.at_least_one_input_shape_matches_the_output_shape(): 'all_input_nodes' are None!",
481480
)
482481

483482
if len(node.all_input_nodes) == 0:
484483
logger.e(
485484
logger.Code.INTERNAL_ERROR,
486-
"node_converter.inputs_satisfy_broadcast_condition(): Operator has no inputs!",
485+
"node_converter.at_least_one_input_shape_matches_the_output_shape(): Operator has no inputs!",
487486
)
488487

489488
output_shape = node.meta["val"].shape
490-
num_elements = prod(output_shape)
491489

492490
return any(
493-
prod(input_tensor.meta["val"].shape) == num_elements
491+
input_tensor.meta["val"].shape == output_shape
494492
for input_tensor in node.all_input_nodes
495493
)

backends/nxp/backend/ir/converter/node_converters/ops_converters/add_tensor_converter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _is_supported_on_target(
2626
parameters_mapping: dict[str, Parameter],
2727
custom_delegation_options: CustomDelegationOptions,
2828
) -> bool:
29-
if not NodeConverter.inputs_satisfy_broadcast_condition(node):
29+
if not NodeConverter.at_least_one_input_shape_matches_the_output_shape(node):
3030
return False
3131

3232
supported_types = [torch.int8, torch.uint8]

backends/nxp/backend/ir/converter/node_converters/ops_converters/mul_tensor_converter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _is_supported_on_target(
2626
parameters_mapping: dict[str, Parameter],
2727
custom_delegation_options: CustomDelegationOptions,
2828
) -> bool:
29-
if not NodeConverter.inputs_satisfy_broadcast_condition(node):
29+
if not NodeConverter.at_least_one_input_shape_matches_the_output_shape(node):
3030
return False
3131

3232
supported_types = [torch.int8, torch.uint8]

backends/nxp/backend/ir/converter/node_converters/ops_converters/prelu_converter.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
# This source code is licensed under the BSD-style license found in the
44
# LICENSE file in the root directory of this source tree.
55

6-
import torch
7-
86
from executorch.backends.nxp.backend.data_format import NXP_NODE_FORMAT
9-
from executorch.backends.nxp.backend.edge_helper import input_rank
107
from executorch.backends.nxp.backend.ir.converter.node_converter import (
118
CustomDelegationOptions,
129
NodeConverter,
@@ -27,17 +24,38 @@ def _is_supported_on_target(
2724
parameters_mapping: dict[str, Parameter],
2825
custom_delegation_options: CustomDelegationOptions,
2926
) -> bool:
30-
if not NodeConverter.inputs_satisfy_broadcast_condition(node):
27+
node_shape = node.meta["val"].shape
28+
rank = len(node_shape)
29+
30+
# According to Neutron spec., PReLU can be done only on 4D tensors
31+
if rank != 4:
32+
return False
33+
34+
ch_idx, h_idx, w_idx = PReLUConverter._get_channel_spatial_indices(node)
35+
# According to Neutron spec., size of channels must be divisible by num_macs.
36+
num_macs = neutron_target_spec.get_num_macs()
37+
if node_shape[ch_idx] % num_macs != 0:
3138
return False
3239

33-
supported_types = [torch.int8, torch.uint8]
34-
if not NodeConverter.uses_quantization_type_for_io(
35-
node, supported_types, [0, 1], [0]
36-
):
40+
# According to Neutron spec., height * width cannot be greater than a given constant.
41+
if node_shape[w_idx] * node_shape[h_idx] > 4096:
3742
return False
3843

3944
return True
4045

46+
@staticmethod
47+
def _get_channel_spatial_indices(node: Node):
48+
if node.meta[NXP_NODE_FORMAT].is_channels_first():
49+
ch_idx = 1
50+
h_idx = 2
51+
w_idx = 3
52+
else:
53+
ch_idx = 3
54+
h_idx = 1
55+
w_idx = 2
56+
57+
return ch_idx, h_idx, w_idx
58+
4159
@staticmethod
4260
def _is_supported_in_IR(
4361
node: Node,
@@ -47,12 +65,6 @@ def _is_supported_in_IR(
4765
if len(node.args) != 2:
4866
return False
4967

50-
if input_rank(node, 0) > 2 and tuple(
51-
node.all_input_nodes[1].meta["val"].shape
52-
) != (1,):
53-
if not node.meta[NXP_NODE_FORMAT].is_channels_first():
54-
# Should never happen.
55-
return False
5668
return True
5769

5870
def convert(self, node: Node):

backends/nxp/backend/ir/converter/node_converters/ops_converters/sub_tensor_converter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _is_supported_on_target(
2626
parameters_mapping: dict[str, Parameter],
2727
custom_delegation_options: CustomDelegationOptions,
2828
) -> bool:
29-
if not NodeConverter.inputs_satisfy_broadcast_condition(node):
29+
if not NodeConverter.at_least_one_input_shape_matches_the_output_shape(node):
3030
return False
3131

3232
supported_types = [torch.int8, torch.uint8]

backends/nxp/backend/node_format_inference.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
from executorch.backends.nxp.backend.data_format import DataFormat, NXP_NODE_FORMAT
1212
from executorch.backends.nxp.backend.edge_helper import (
13-
input_rank,
1413
is_channels_last_dim_order,
1514
try_get_arg,
1615
)
@@ -27,7 +26,6 @@
2726
MaxPool2DWithIndices,
2827
MeanDim,
2928
PermuteCopy,
30-
Prelu,
3129
QuantizePerTensor,
3230
SumDimIntList,
3331
UpsampleBilinear2D,
@@ -56,11 +54,6 @@ class NodeFormatInference:
5654
UpsampleNearest2D: {"inputs": [0]},
5755
}
5856

59-
ops_conditionally_with_channels_first_nodes = {
60-
Prelu: {"inputs": [0]},
61-
torch.ops.aten.prelu.default: {"inputs": [0]},
62-
}
63-
6457
# A set of Edge Aten ops, which have the ability to change the format (for example - input nodes
6558
# are channels first but output is formatless).
6659
ops_that_can_change_tensor_format = {
@@ -71,8 +64,6 @@ class NodeFormatInference:
7164
SumDimIntList,
7265
}
7366

74-
prelu_targets = [Prelu, torch.ops.aten.prelu.default]
75-
7667
_type_changed_during_last_run: bool
7768

7869
# Mapping between Node and its ancestors (inputs)
@@ -169,13 +160,6 @@ def _infer_format_of_nodes(self, node: Node):
169160
f"Node format inference for node type: {op_type} not found!"
170161
)
171162

172-
elif node.target in self.prelu_targets:
173-
num_parameters_shape = tuple(node.args[1].meta["val"].shape)
174-
if input_rank(node, 0) > 2 and num_parameters_shape != (1,):
175-
self._handle_node_which_uses_channels_first_format(node)
176-
else:
177-
self._handle_node_which_can_use_any_node_format(node)
178-
179163
elif node.op != "call_function" or (
180164
hasattr(node, "target") and node.target in self._known_targets
181165
):
@@ -264,14 +248,10 @@ def _handle_node_which_uses_channels_first_format(self, node: Node):
264248
Function for assigning format to nodes that require channels first input (Conv, MaxPool etc.)
265249
"""
266250
op_type = self._get_node_op_type(node)
267-
ops_using_channels_first_format = (
268-
self.ops_with_channels_first_nodes
269-
| self.ops_conditionally_with_channels_first_nodes
270-
)
271251

272252
for index, ancestor_node in enumerate(self._node_inputs[node]):
273253
# Go through input nodes and assign them correct format
274-
if index in ops_using_channels_first_format[op_type]["inputs"]:
254+
if index in self.ops_with_channels_first_nodes[op_type]["inputs"]:
275255
self._assign_format_to_node(ancestor_node, DataFormat.CHANNELS_FIRST)
276256

277257
# We need to propagate channels first format up to already visited nodes

backends/nxp/tests/ir/converter/node_converter/test_add_tensor_converter.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,6 @@ def test__basic_nsys_inference_qat(self, mocker, request):
115115
[ModelInputSpec((1, 4, 8, 8)), ModelInputSpec((8, 8))],
116116
id="2 inputs 4D + 2D.",
117117
),
118-
pytest.param(
119-
[ModelInputSpec((10,)), ModelInputSpec((1, 1))],
120-
id="2 inputs 1D + 2D, num_elems of input == num_elems of output",
121-
),
122118
],
123119
)
124120
def test__broadcast(self, mocker, request, input_spec):

0 commit comments

Comments
 (0)