|
11 | 11 | import torch |
12 | 12 |
|
13 | 13 | from executorch.backends.xnnpack._passes import XNNPACKPassManager |
| 14 | +from executorch.backends.xnnpack._passes.channels_last_tagged_reshape_pass import ( |
| 15 | + ChannelsLastTaggedReshapePass, |
| 16 | +) |
14 | 17 | from executorch.backends.xnnpack._passes.convert_to_linear import ConvertToLinearPass |
15 | 18 | from executorch.backends.xnnpack.operators.node_visitor import get_node_visitors |
16 | 19 |
|
17 | 20 | from executorch.backends.xnnpack.serialization.xnnpack_graph_schema import ( |
18 | 21 | ConstantDataOffset, |
19 | 22 | XNNGraph, |
| 23 | + XNNQuantizedTensorValue, |
| 24 | + XNNStaticTranspose, |
| 25 | + XNNTensorValue, |
| 26 | + XNode, |
| 27 | + XValue, |
20 | 28 | ) |
21 | 29 | from executorch.backends.xnnpack.serialization.xnnpack_graph_serialize import ( |
22 | 30 | serialize_xnnpack_binary, |
|
25 | 33 | from executorch.backends.xnnpack.utils.utils import is_param_node |
26 | 34 |
|
27 | 35 | from executorch.backends.xnnpack.utils.xnnpack_constants import ( |
| 36 | + XNN_INVALID_VALUE_ID, |
28 | 37 | XNN_VALUE_FLAG_EXTERNAL_INPUT, |
29 | 38 | XNN_VALUE_FLAG_EXTERNAL_OUTPUT, |
30 | 39 | ) |
@@ -99,6 +108,86 @@ def assert_default_dim_order(edge_graph_module: torch.fx.GraphModule) -> None: |
99 | 108 | ) |
100 | 109 |
|
101 | 110 |
|
| 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 | + |
102 | 191 | @final |
103 | 192 | class XnnpackBackend(BackendDetails): |
104 | 193 | @staticmethod |
@@ -185,6 +274,8 @@ def preprocess( |
185 | 274 | continue |
186 | 275 | else: |
187 | 276 | 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) |
188 | 279 | return PreprocessResult( |
189 | 280 | processed_bytes=serialize_xnnpack_binary( |
190 | 281 | xnnpack_graph, constant_data_bytes |
|
0 commit comments