@@ -94,7 +94,7 @@ def _adjust_pad_if_needed(
9494 pass instead.
9595
9696 """
97- mod_remainder = (
97+ mod_remainder : int | torch . SymInt = (
9898 input_len + 2 * pad - dilation * (input_weight - 1 ) - 1
9999 ) % stride
100100
@@ -121,14 +121,14 @@ def _adjust_pad_if_needed(
121121
122122 return pad - mod_remainder
123123
124- def _is_depthwise_conv2d (self , node : torch .fx .Node ) -> bool :
124+ def _is_depthwise_conv (self , node : torch .fx .Node ) -> bool :
125125 if (
126126 node .op != "call_function"
127127 or node .target != exir_ops .edge .aten .convolution .default
128128 ):
129129 return False
130130 input_tensor = get_first_fake_tensor (node .all_input_nodes [0 ])
131- if len (input_tensor .shape ) != 4 :
131+ if len (input_tensor .shape ) not in ( 3 , 4 ) :
132132 return False
133133 groups = node .args [- 1 ]
134134 in_channels = input_tensor .shape [1 ]
@@ -524,7 +524,7 @@ def _combine_rescale_scales(
524524 @staticmethod
525525 def _is_direct_int32_rescale (node : torch .fx .Node ) -> bool :
526526 """Return whether a node directly rescales its input to INT32."""
527- return (
527+ return bool (
528528 node .op == "call_function"
529529 and node .target == exir_ops .backend .tosa .RESCALE .default
530530 and len (node .args ) > 1
@@ -911,7 +911,70 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
911911 dilation = tuple (dilation_list )
912912 pad = pad_attr
913913
914- if self ._is_conv3d (len (input_shape ), group ):
914+ if spatial_rank == 1 :
915+ target_op = (
916+ exir_ops .backend .tosa .DEPTHWISE_CONV2D .default
917+ if self ._is_depthwise_conv (node )
918+ else exir_ops .backend .tosa .CONV2D .default
919+ )
920+ pre_permute_dims = (0 , 2 , 1 )
921+ post_permute_dims = (0 , 2 , 1 )
922+ with graph_module .graph .inserting_before (node ):
923+ x = create_node (
924+ graph = graph_module .graph ,
925+ op_target = exir_ops .edge .aten .permute_copy .default ,
926+ args = (x , list (pre_permute_dims )),
927+ from_node = node ,
928+ )
929+ permuted_input_fake = permute_fake_tensor_metadata (
930+ input_fake_tensor , pre_permute_dims
931+ )
932+ x .meta ["val" ] = permuted_input_fake
933+ input_tensor_for_tosa_fake = permuted_input_fake .unsqueeze (1 )
934+ x = create_node (
935+ graph = graph_module .graph ,
936+ op_target = exir_ops .edge .aten .view_copy .default ,
937+ args = (x , list (input_tensor_for_tosa_fake .shape )),
938+ from_node = node ,
939+ )
940+ x .meta ["val" ] = input_tensor_for_tosa_fake
941+
942+ kernel_width = weight_shape [2 ]
943+ if target_op == exir_ops .backend .tosa .DEPTHWISE_CONV2D .default :
944+ in_channels = input_fake_tensor .shape [1 ]
945+ channel_multiplier = weight_shape [0 ] // in_channels
946+ weight = self ._rewrite_weight (
947+ graph_module ,
948+ weight ,
949+ node ,
950+ permute_dims = (1 , 2 , 0 ),
951+ name_suffix = "hwicm" ,
952+ reshape_dims = (
953+ 1 ,
954+ kernel_width ,
955+ in_channels ,
956+ channel_multiplier ,
957+ ),
958+ )
959+ else :
960+ weight = self ._rewrite_weight (
961+ graph_module ,
962+ weight ,
963+ node ,
964+ permute_dims = (0 , 2 , 1 ),
965+ name_suffix = "ohwi" ,
966+ reshape_dims = (
967+ weight_shape [0 ],
968+ 1 ,
969+ kernel_width ,
970+ weight_shape [1 ],
971+ ),
972+ )
973+ weight_fake_tensor = get_first_fake_tensor (weight )
974+ stride = (1 , stride [0 ])
975+ dilation = (1 , dilation [0 ])
976+ pad = [0 , 0 , pad [0 ], pad [1 ]]
977+ elif self ._is_conv3d (len (input_shape ), group ):
915978 target_op = exir_ops .backend .tosa .CONV3D .default
916979 pre_permute_dims = ODHWI_ORDER
917980 post_permute_dims = ODHWI_INVERSE_ORDER
@@ -934,7 +997,7 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
934997 name_suffix = "odhwi" ,
935998 )
936999 weight_fake_tensor = get_first_fake_tensor (weight )
937- elif self ._is_depthwise_conv2d (node ):
1000+ elif self ._is_depthwise_conv (node ):
9381001 target_op = exir_ops .backend .tosa .DEPTHWISE_CONV2D .default
9391002 pre_permute_dims = NHWC_ORDER
9401003 post_permute_dims = NHWC_INVERSE_ORDER
@@ -1039,7 +1102,28 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
10391102
10401103 if post_permute_dims is None :
10411104 raise RuntimeError ("Expected post permute dims for explicit layout" )
1105+ output_conversion_node = node_replacement
10421106 post_permute_input = node_replacement
1107+ squeeze_view : torch .fx .Node | None = None
1108+ if spatial_rank == 1 :
1109+ squeezed_output_fake = cast (
1110+ FakeTensor , node_replacement_fake_tensor .squeeze (1 )
1111+ )
1112+ special_dtype = node_replacement .meta .get (TosaSpecialDtype .meta_key ())
1113+ with graph_module .graph .inserting_after (node_replacement ):
1114+ node_replacement = create_node (
1115+ graph = graph_module .graph ,
1116+ op_target = exir_ops .edge .aten .view_copy .default ,
1117+ args = (node_replacement , list (squeezed_output_fake .shape )),
1118+ from_node = node ,
1119+ )
1120+ node_replacement .meta ["val" ] = squeezed_output_fake
1121+ if special_dtype :
1122+ node_replacement .meta [TosaSpecialDtype .meta_key ()] = special_dtype
1123+ squeeze_view = node_replacement
1124+ post_permute_input = node_replacement
1125+ node_replacement_fake_tensor = squeezed_output_fake
1126+
10431127 with graph_module .graph .inserting_after (node_replacement ):
10441128 node_replacement = create_node (
10451129 graph = graph_module .graph ,
@@ -1059,16 +1143,23 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
10591143 tosa_node_fake_tensor .dtype == torch .int32
10601144 and input_fake_tensor .dtype == torch .int16
10611145 )
1062- if is_a16w8_conv :
1146+ if is_a16w8_conv and spatial_rank != 1 :
10631147 # Keep values in INT32 whenever a consumer supports it, even
10641148 # though the declared output is INT16, by branching from the
10651149 # accumulator before narrowing.
1150+ #
1151+ # Rank-three convolutions are excluded. The legacy Conv1d
1152+ # expansion placed a rank-changing view between the convolution
1153+ # and its INT32 consumers, so the convolution narrowed to its
1154+ # exported output domain instead of forking. Forking here would
1155+ # give each branch its own boundary rescale and permute, which
1156+ # Vela materialises as a second full transpose of the output.
10661157 self ._insert_a16w8_output_branches (
10671158 graph_module ,
10681159 node ,
10691160 tosa_op ,
10701161 tosa_node_fake_tensor ,
1071- post_permute_input ,
1162+ output_conversion_node ,
10721163 post_permute_dims ,
10731164 )
10741165 # Only users not moved to widened branches remain on the
@@ -1078,7 +1169,9 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
10781169 node .replace_all_uses_with (node_replacement )
10791170 else :
10801171 graph_module .graph .erase_node (node_replacement )
1081- graph_module .graph .erase_node (post_permute_input )
1172+ if squeeze_view is not None :
1173+ graph_module .graph .erase_node (squeeze_view )
1174+ graph_module .graph .erase_node (output_conversion_node )
10821175 else :
10831176 node .replace_all_uses_with (node_replacement )
10841177
0 commit comments