@@ -785,3 +785,43 @@ TEST_F(OpConvCorrectnessTest, BFloat16TypeSmokeTest) {
785785 out);
786786 EXPECT_TENSOR_CLOSE (out, expected);
787787}
788+
789+ // Regression test for #20804: a transposed convolution weight with out_channels == 1
790+ // arrives with a non-default dim order (e.g. [1, 0, 2, 3], since the weight is laid out
791+ // as (in_channels, out_channels / groups, kH, kW)). The kernel indexes the weight through
792+ // its strides, so it must run and produce the same result as a default-order weight.
793+ TEST_F (OpConvCorrectnessTest, TransposedWeightNonDefaultDimOrder) {
794+ TensorFactory<ScalarType::Float> tf;
795+
796+ Tensor input = tf.make ({1 , 2 , 2 , 2 }, {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 });
797+
798+ // Weight (in_channels=2, out_channels=1, kH=2, kW=2) in dim order [1, 0, 2, 3].
799+ Tensor weight = tf.make_with_dimorder (
800+ {2 , 1 , 2 , 2 }, {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 }, /* dim_order=*/ {1 , 0 , 2 , 3 });
801+
802+ optional<Tensor> bias;
803+ Tensor out = tf.zeros ({1 , 1 , 3 , 3 });
804+ Tensor expected =
805+ tf.make ({1 , 1 , 3 , 3 }, {26 , 64 , 40 , 76 , 184 , 112 , 58 , 136 , 80 });
806+
807+ int64_t stride[2 ] = {1 , 1 };
808+ int64_t padding[2 ] = {0 , 0 };
809+ int64_t dilation[2 ] = {1 , 1 };
810+ bool transposed = true ;
811+ int64_t output_padding[2 ] = {0 , 0 };
812+ int64_t groups = 1 ;
813+
814+ op_convolution_out (
815+ input,
816+ weight,
817+ std::optional<Tensor>(bias),
818+ executorch::aten::ArrayRef<int64_t >{stride, 2 },
819+ executorch::aten::ArrayRef<int64_t >{padding, 2 },
820+ executorch::aten::ArrayRef<int64_t >{dilation, 2 },
821+ transposed,
822+ executorch::aten::ArrayRef<int64_t >{output_padding, 2 },
823+ groups,
824+ out);
825+
826+ EXPECT_TENSOR_CLOSE (out, expected);
827+ }
0 commit comments