Skip to content

Commit d5805e4

Browse files
SofianElmotiemJakeStevens
authored andcommitted
Fix optimized le for reverse-argument broadcasts
When the left operand is the broadcast one, select_optimized_path picks a *ReverseArguments path and handle_broadcast_elementwise calls the lambda with the operands swapped. op_div and op_add_sub_impl swap their lambda for those paths, but op_le did not, so le(a, b) returned b <= a whenever the inputs and output shared a dtype. Swap the lambda the same way. The existing reverse broadcast tests use a Bool output, which routes to the portable path, so the new tests use a same-dtype output and Bool inputs to exercise the optimized paths. Fixes #24485 Authored with AI assistance (Claude Code).
1 parent 3f08f3f commit d5805e4

2 files changed

Lines changed: 68 additions & 3 deletions

File tree

‎kernels/optimized/cpu/op_le.cpp‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,20 @@ Tensor& opt_le_tensor_out(
5757
} else if (selected_optimized_path != ElementwiseOptimizedPath::kNone) {
5858
// Handle optimized broadcast cases
5959
ET_SWITCH_REALB_TYPES(out_type, ctx, op_name, CTYPE, [&]() {
60-
auto le_lambda = [](auto x, auto y) { return x.le(y); };
61-
torch::executor::handle_broadcast_elementwise<CTYPE>(
62-
ctx, le_lambda, a, b, out, selected_optimized_path);
60+
if (selected_optimized_path ==
61+
ElementwiseOptimizedPath::kBroadcast2dBy1dReverseArguments ||
62+
selected_optimized_path ==
63+
ElementwiseOptimizedPath::kBroadcastLastDimReverseArguments ||
64+
selected_optimized_path ==
65+
ElementwiseOptimizedPath::kBroadcastNdByNdReverseArguments) {
66+
auto le_lambda = [](auto x, auto y) { return y.le(x); };
67+
torch::executor::handle_broadcast_elementwise<CTYPE>(
68+
ctx, le_lambda, a, b, out, selected_optimized_path);
69+
} else {
70+
auto le_lambda = [](auto x, auto y) { return x.le(y); };
71+
torch::executor::handle_broadcast_elementwise<CTYPE>(
72+
ctx, le_lambda, a, b, out, selected_optimized_path);
73+
}
6374
});
6475
} else {
6576
internal::comparison_tensor_out<std::less_equal, op_name>(ctx, a, b, out);

‎kernels/test/op_le_test.cpp‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,41 @@ class OpLeTensorOutTest : public OperatorTest {
6161
op_le_tensor_out(a, b, out);
6262
EXPECT_TENSOR_EQ(out, tf_out.make({2, 2}, {false, true, true, false}));
6363
}
64+
65+
// Output dtype matches the inputs so the optimized broadcast paths are taken.
66+
template <ScalarType DTYPE>
67+
void test_broadcast_same_dtype_out() {
68+
TensorFactory<DTYPE> tf;
69+
70+
// kBroadcast2dBy1d
71+
Tensor x = tf.make({4}, {1, 2, 3, 4});
72+
Tensor y = tf.make({2, 4}, {0, 2, 4, 6, 4, 3, 2, 1});
73+
Tensor out = tf.zeros({2, 4});
74+
EXPECT_TENSOR_EQ(
75+
op_le_tensor_out(x, y, out), tf.make({2, 4}, {0, 1, 1, 1, 1, 1, 0, 0}));
76+
EXPECT_TENSOR_EQ(
77+
op_le_tensor_out(y, x, out), tf.make({2, 4}, {1, 1, 0, 0, 0, 0, 1, 1}));
78+
79+
// kBroadcastLastDim
80+
x = tf.make({2, 1}, {2, 3});
81+
y = tf.make({2, 3}, {1, 2, 3, 4, 3, 2});
82+
out = tf.zeros({2, 3});
83+
EXPECT_TENSOR_EQ(
84+
op_le_tensor_out(x, y, out), tf.make({2, 3}, {0, 1, 1, 1, 1, 0}));
85+
EXPECT_TENSOR_EQ(
86+
op_le_tensor_out(y, x, out), tf.make({2, 3}, {1, 1, 0, 0, 1, 1}));
87+
88+
// kBroadcastNdByNd
89+
x = tf.make({2, 1, 2}, {1, 4, 3, 2});
90+
y = tf.make({2, 3, 2}, {0, 4, 1, 5, 2, 3, 3, 1, 4, 2, 2, 0});
91+
out = tf.zeros({2, 3, 2});
92+
EXPECT_TENSOR_EQ(
93+
op_le_tensor_out(x, y, out),
94+
tf.make({2, 3, 2}, {0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0}));
95+
EXPECT_TENSOR_EQ(
96+
op_le_tensor_out(y, x, out),
97+
tf.make({2, 3, 2}, {1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1}));
98+
}
6499
};
65100

66101
TEST_F(OpLeScalarOutTest, AllRealInputBoolOutputSupport) {
@@ -1179,3 +1214,22 @@ TEST_F(OpLeTensorOutTest, MonotonicIncreasingVsScalarBroadcastTest) {
11791214

11801215
EXPECT_TENSOR_EQ(out, tf_bool.make({1, 64}, expected_data));
11811216
}
1217+
1218+
TEST_F(OpLeTensorOutTest, BroadcastSameDtypeOut) {
1219+
test_broadcast_same_dtype_out<ScalarType::Float>();
1220+
test_broadcast_same_dtype_out<ScalarType::Int>();
1221+
}
1222+
1223+
TEST_F(OpLeTensorOutTest, BroadcastBoolInputs) {
1224+
TensorFactory<ScalarType::Bool> tf;
1225+
Tensor x = tf.make({4}, {false, true, false, true});
1226+
Tensor y =
1227+
tf.make({2, 4}, {false, false, true, true, true, true, false, false});
1228+
Tensor out = tf.zeros({2, 4});
1229+
EXPECT_TENSOR_EQ(
1230+
op_le_tensor_out(x, y, out),
1231+
tf.make({2, 4}, {true, false, true, true, true, true, true, false}));
1232+
EXPECT_TENSOR_EQ(
1233+
op_le_tensor_out(y, x, out),
1234+
tf.make({2, 4}, {true, true, false, true, false, true, true, true}));
1235+
}

0 commit comments

Comments
 (0)