Skip to content

Commit 8a67662

Browse files
authored
Merge pull request #22 from software-mansion-labs/@ms/xnnpack-input-dim-order-1.4.1
XNNPACK: read inputs with a non-default dim order in their memory layout
2 parents b131499 + 6e97776 commit 8a67662

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

‎backends/xnnpack/test/test_xnnpack_partitioner.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,32 @@ def example_inputs(self):
161161
fwd2_et = executorch_module.run_method("forward_2", example_inputs)
162162
self.assertTrue(torch.allclose(fwd1_eager, fwd1_et[0], 1e-3))
163163
self.assertTrue(torch.allclose(fwd2_eager, fwd2_et[0], 1e-3))
164+
165+
def test_input_with_non_contiguous_dim_order(self):
166+
"""
167+
A delegate input whose dim order is neither contiguous nor channels-last,
168+
here (1, 0, 2) because the example input is a transposed view, must be
169+
read in its memory layout. Previously its sizes were serialized in
170+
logical order while the runtime reshapes it in dim order, so shape
171+
propagation failed with xnn_status_invalid_parameter.
172+
"""
173+
174+
class BatchMatmul(torch.nn.Module):
175+
def forward(self, x, y):
176+
return torch.bmm(x, y)
177+
178+
x = torch.randn(4, 8, 16).transpose(0, 1)
179+
y = torch.randn(8, 16, 4)
180+
self.assertEqual(x.dim_order(), (1, 0, 2))
181+
n = torch.export.Dim("n", min=2, max=8)
182+
exported = export(BatchMatmul(), (x, y), dynamic_shapes=({1: n}, {2: n}))
183+
executorch = to_edge_transform_and_lower(
184+
exported, partitioner=[XnnpackPartitioner()]
185+
).to_executorch()
186+
187+
# The bindings copy an input's buffer as is, so hand over x's bytes in
188+
# its memory order (dim order (1, 0, 2)) under its logical shape.
189+
x_in_memory_order = x.transpose(0, 1).contiguous().view(x.shape)
190+
executorch_module = _load_for_executorch_from_buffer(executorch.buffer)
191+
out = executorch_module.run_method("forward", (x_in_memory_order, y))
192+
self.assertTrue(torch.allclose(torch.bmm(x, y), out[0], atol=1e-5))

‎backends/xnnpack/xnnpack_preprocess.py‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,20 @@
1111
import torch
1212

1313
from executorch.backends.xnnpack._passes import XNNPACKPassManager
14+
from executorch.backends.xnnpack._passes.channels_last_tagged_reshape_pass import (
15+
ChannelsLastTaggedReshapePass,
16+
)
1417
from executorch.backends.xnnpack._passes.convert_to_linear import ConvertToLinearPass
1518
from executorch.backends.xnnpack.operators.node_visitor import get_node_visitors
1619

1720
from executorch.backends.xnnpack.serialization.xnnpack_graph_schema import (
1821
ConstantDataOffset,
1922
XNNGraph,
23+
XNNQuantizedTensorValue,
24+
XNNStaticTranspose,
25+
XNNTensorValue,
26+
XNode,
27+
XValue,
2028
)
2129
from executorch.backends.xnnpack.serialization.xnnpack_graph_serialize import (
2230
serialize_xnnpack_binary,
@@ -25,6 +33,7 @@
2533
from executorch.backends.xnnpack.utils.utils import is_param_node
2634

2735
from executorch.backends.xnnpack.utils.xnnpack_constants import (
36+
XNN_INVALID_VALUE_ID,
2837
XNN_VALUE_FLAG_EXTERNAL_INPUT,
2938
XNN_VALUE_FLAG_EXTERNAL_OUTPUT,
3039
)
@@ -99,6 +108,86 @@ def assert_default_dim_order(edge_graph_module: torch.fx.GraphModule) -> None:
99108
)
100109

101110

111+
def define_inputs_in_memory_order(
112+
xnn_graph: XNNGraph,
113+
node_to_external_map: Dict[torch.fx.Node, ExternalMeta],
114+
vals_to_ids: Dict[torch.fx.Node, int],
115+
) -> None:
116+
"""
117+
Declares each graph input whose dim order is neither contiguous nor
118+
channels-last in its memory order, followed by a static transpose back to
119+
the logical layout the rest of the XNNPACK graph was built for.
120+
121+
At runtime XNNExecutor reshapes every external input with its sizes listed
122+
in dim order, i.e. as the buffer is laid out in memory. The values here are
123+
defined from the logical shape, so an input with, e.g., dim order (1, 0, 2)
124+
would otherwise be reshaped to a transposed shape and either fail shape
125+
propagation or be read with its axes swapped.
126+
"""
127+
xvalues_by_id = {}
128+
for xvalue in xnn_graph.xvalues:
129+
union = xvalue.xvalue_union
130+
tensor_value = (
131+
union.tensor_value if isinstance(union, XNNQuantizedTensorValue) else union
132+
)
133+
xvalues_by_id[tensor_value.id_out] = xvalue
134+
135+
prologue = []
136+
for node, meta in node_to_external_map.items():
137+
if meta.io_type != XNN_VALUE_FLAG_EXTERNAL_INPUT or node not in vals_to_ids:
138+
continue
139+
val = node.meta.get("val", None)
140+
if not isinstance(val, torch.Tensor):
141+
continue
142+
dim_order = tuple(val.dim_order())
143+
if dim_order == tuple(range(val.dim())):
144+
continue
145+
if ChannelsLastTaggedReshapePass._is_nhwc_tensor(val):
146+
# ChannelsLastTaggedReshapePass already bridges NHWC inputs.
147+
continue
148+
149+
logical_id = vals_to_ids[node]
150+
xvalue = xvalues_by_id[logical_id]
151+
logical = xvalue.xvalue_union
152+
if isinstance(logical, XNNQuantizedTensorValue):
153+
raise RuntimeError(
154+
f"XNNPACK does not support quantized input {node} with dim order "
155+
f"{dim_order}; only contiguous or channels-last inputs are supported."
156+
)
157+
158+
memory_id = len(xnn_graph.xvalues)
159+
memory = XNNTensorValue(
160+
datatype=logical.datatype,
161+
num_dims=logical.num_dims,
162+
dims=[logical.dims[d] for d in dim_order],
163+
constant_buffer_idx=0,
164+
external_id=logical.external_id,
165+
flags=logical.flags,
166+
id_out=memory_id,
167+
)
168+
xnn_graph.xvalues.append(XValue(xvalue_union=memory))
169+
logical.external_id = XNN_INVALID_VALUE_ID
170+
logical.flags = 0
171+
xnn_graph.input_ids = [
172+
memory_id if i == logical_id else i for i in xnn_graph.input_ids
173+
]
174+
175+
# Logical dim i is memory dim dim_order.index(i).
176+
prologue.append(
177+
XNode(
178+
xnode_union=XNNStaticTranspose(
179+
num_dims=len(dim_order),
180+
perm=[dim_order.index(i) for i in range(len(dim_order))],
181+
input_id=memory_id,
182+
output_id=logical_id,
183+
flags=0,
184+
),
185+
debug_handle=DEFAULT_DEBUG_HANDLE,
186+
)
187+
)
188+
xnn_graph.xnodes[:0] = prologue
189+
190+
102191
@final
103192
class XnnpackBackend(BackendDetails):
104193
@staticmethod
@@ -185,6 +274,8 @@ def preprocess(
185274
continue
186275
else:
187276
raise RuntimeError(f"{node.op} is not supported in XNNPACK")
277+
278+
define_inputs_in_memory_order(xnnpack_graph, node_to_external_map, vals_to_ids)
188279
return PreprocessResult(
189280
processed_bytes=serialize_xnnpack_binary(
190281
xnnpack_graph, constant_data_bytes

0 commit comments

Comments
 (0)