Skip to content

Commit 1a770b8

Browse files
authored
Fix optimized binary op broadcast rank mismatch (pytorch#21052)
### Summary The optimized last-dimension and planned broadcast paths resize `out` to one input's shape after the operator has already selected the broadcast target. When leading dimensions of size one make the target rank larger, that second resize tries to change the immutable rank and fails at runtime. Resize `out` to the broadcast target in both paths and add regression coverage for add and mul in both argument orders. Fixes pytorch#10959 ### Test plan `./cmake-out/kernels/test/optimized_kernels_test --gtest_brief=1` `lintrunner kernels/optimized/cpu/binary_ops.cpp kernels/optimized/cpu/binary_ops.h kernels/test/op_add_test.cpp kernels/test/op_mul_test.cpp` cc @larryliu0820 @manuelcandales
1 parent c48a7b2 commit 1a770b8

4 files changed

Lines changed: 50 additions & 4 deletions

File tree

kernels/optimized/cpu/binary_ops.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,9 @@ std::optional<BroadcastElementwisePlan> plan_broadcast_elementwise(
3232
plan.lhs = &a;
3333
plan.rhs = &b;
3434
}
35-
auto error = resize_tensor(out, plan.lhs->sizes());
3635
ET_KERNEL_CHECK_MSG(
3736
ctx,
38-
error == Error::Ok,
37+
resize_to_broadcast_target_size(a, b, out) == Error::Ok,
3938
InvalidArgument,
4039
std::nullopt,
4140
"Failed to resize output tensor.");

kernels/optimized/cpu/binary_ops.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <executorch/kernels/optimized/vec/functional.h>
1212
#include <executorch/kernels/portable/cpu/scalar_utils.h>
1313
#include <executorch/kernels/portable/cpu/util/broadcast_indexes_range.h>
14+
#include <executorch/kernels/portable/cpu/util/broadcast_util.h>
1415
#include <executorch/runtime/kernel/kernel_includes.h>
1516

1617
#include <optional>
@@ -189,10 +190,9 @@ Tensor& handle_last_dim_broadcast_elementwise(
189190
lhs = &a;
190191
rhs = &b;
191192
}
192-
auto error = resize_tensor(out, lhs->sizes());
193193
ET_KERNEL_CHECK_MSG(
194194
ctx,
195-
error == Error::Ok,
195+
resize_to_broadcast_target_size(a, b, out) == Error::Ok,
196196
InvalidArgument,
197197
out,
198198
"Failed to resize output tensor.");

kernels/test/op_add_test.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,29 @@ TEST_F(OpAddOutKernelTest, BroadcastBToA) {
604604
EXPECT_TENSOR_CLOSE(op_add_out(a, b, 1.0, out), expected);
605605
}
606606

607+
TEST_F(OpAddOutKernelTest, BroadcastLastDimRankMismatch) {
608+
TensorFactory<ScalarType::Float> tf;
609+
Tensor a = tf.make({3, 4}, /*data=*/{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
610+
Tensor b = tf.make({1, 3, 1}, /*data=*/{10, 20, 30});
611+
Tensor out = tf.zeros({1, 3, 4});
612+
613+
Tensor expected = tf.make(
614+
{1, 3, 4}, /*data=*/{11, 12, 13, 14, 25, 26, 27, 28, 39, 40, 41, 42});
615+
EXPECT_TENSOR_CLOSE(op_add_out(a, b, 1.0, out), expected);
616+
EXPECT_TENSOR_CLOSE(op_add_out(b, a, 1.0, out), expected);
617+
}
618+
619+
TEST_F(OpAddOutKernelTest, Broadcast2dBy1dRankMismatch) {
620+
TensorFactory<ScalarType::Float> tf;
621+
Tensor a = tf.make({2, 3}, /*data=*/{1, 2, 3, 4, 5, 6});
622+
Tensor b = tf.make({1, 1, 3}, /*data=*/{10, 20, 30});
623+
Tensor out = tf.zeros({1, 2, 3});
624+
625+
Tensor expected = tf.make({1, 2, 3}, /*data=*/{11, 22, 33, 14, 25, 36});
626+
EXPECT_TENSOR_CLOSE(op_add_out(a, b, 1.0, out), expected);
627+
EXPECT_TENSOR_CLOSE(op_add_out(b, a, 1.0, out), expected);
628+
}
629+
607630
//
608631
// Death Tests
609632
//

kernels/test/op_mul_test.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,3 +923,27 @@ TEST_F(OpMulOutTest, BroadcastDimensionMismatchWithDifferentTypes) {
923923
EXPECT_TENSOR_EQ(result, expected);
924924
}
925925
}
926+
927+
TEST_F(OpMulOutTest, BroadcastLastDimRankMismatch) {
928+
TensorFactory<ScalarType::Float> tf;
929+
Tensor a = tf.make({3, 4}, /*data=*/{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
930+
Tensor b = tf.make({1, 3, 1}, /*data=*/{10, 20, 30});
931+
Tensor out = tf.zeros({1, 3, 4});
932+
933+
Tensor expected = tf.make(
934+
{1, 3, 4},
935+
/*data=*/{10, 20, 30, 40, 100, 120, 140, 160, 270, 300, 330, 360});
936+
EXPECT_TENSOR_CLOSE(op_mul_out(a, b, out), expected);
937+
EXPECT_TENSOR_CLOSE(op_mul_out(b, a, out), expected);
938+
}
939+
940+
TEST_F(OpMulOutTest, Broadcast2dBy1dRankMismatch) {
941+
TensorFactory<ScalarType::Float> tf;
942+
Tensor a = tf.make({2, 3}, /*data=*/{1, 2, 3, 4, 5, 6});
943+
Tensor b = tf.make({1, 1, 3}, /*data=*/{10, 20, 30});
944+
Tensor out = tf.zeros({1, 2, 3});
945+
946+
Tensor expected = tf.make({1, 2, 3}, /*data=*/{10, 40, 90, 40, 100, 180});
947+
EXPECT_TENSOR_CLOSE(op_mul_out(a, b, out), expected);
948+
EXPECT_TENSOR_CLOSE(op_mul_out(b, a, out), expected);
949+
}

0 commit comments

Comments
 (0)