blob: d7ed825640072de89c8581ef78b629b1bd600bbe [file] [log] [blame]
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <executorch/kernels/test/FunctionHeaderWrapper.h> // Declares the operator
#include <executorch/kernels/test/TestUtil.h>
#include <executorch/kernels/test/supported_features.h>
#include <executorch/runtime/core/exec_aten/exec_aten.h>
#include <executorch/runtime/core/exec_aten/testing_util/tensor_factory.h>
#include <executorch/runtime/core/exec_aten/testing_util/tensor_util.h>
#include <gtest/gtest.h>
using namespace ::testing;
using exec_aten::Scalar;
using exec_aten::ScalarType;
using exec_aten::Tensor;
using torch::executor::testing::TensorFactory;
class OpMaskedFillTest : public OperatorTest {
protected:
Tensor& op_masked_fill_scalar_out(
const Tensor& self,
const Tensor& mask,
const Scalar& value,
Tensor& out) {
return torch::executor::aten::masked_fill_outf(
context_, self, mask, value, out);
}
// Common testing for masked fill of integer Tensor.
template <ScalarType DTYPE>
void test_integer_masked_fill_scalar_out() {
TensorFactory<DTYPE> tf;
TensorFactory<ScalarType::Bool> tf_bool;
const std::vector<int32_t> sizes = {2, 2};
// Destination for the masked_fill.
Tensor out = tf.zeros(sizes);
// Masked fill half of the tensor.
op_masked_fill_scalar_out(
tf.make(sizes, /*data=*/{23, 29, 31, 37}),
tf_bool.make(sizes, /*data=*/{false, true, true, false}),
/*value=*/71,
out);
// Check that it matches the expected output.
EXPECT_TENSOR_EQ(out, tf.make(sizes, /*data=*/{23, 71, 71, 37}));
}
// Common testing for masked fill of floating point Tensor.
template <ScalarType DTYPE>
void test_floating_point_masked_fill_scalar_out() {
TensorFactory<DTYPE> tf;
TensorFactory<ScalarType::Bool> tf_bool;
const std::vector<int32_t> sizes = {2, 2};
// Destination for the masked_fill.
Tensor out = tf.zeros(sizes);
// Masked fill half of the tensor.
op_masked_fill_scalar_out(
tf.make(sizes, /*data=*/{1.1, 2.2, 4.4, 8.8}),
tf_bool.make(sizes, /*data=*/{true, false, false, true}),
/*value=*/3.3,
out);
// Check that it matches the expected output.
EXPECT_TENSOR_CLOSE(out, tf.make(sizes, /*data=*/{3.3, 2.2, 4.4, 3.3}));
}
};
TEST_F(OpMaskedFillTest, ByteTensors) {
test_integer_masked_fill_scalar_out<ScalarType::Byte>();
}
TEST_F(OpMaskedFillTest, CharTensors) {
test_integer_masked_fill_scalar_out<ScalarType::Char>();
}
TEST_F(OpMaskedFillTest, ShortTensors) {
test_integer_masked_fill_scalar_out<ScalarType::Short>();
}
TEST_F(OpMaskedFillTest, IntTensors) {
test_integer_masked_fill_scalar_out<ScalarType::Int>();
}
TEST_F(OpMaskedFillTest, LongTensors) {
test_integer_masked_fill_scalar_out<ScalarType::Long>();
}
TEST_F(OpMaskedFillTest, IntTensorFloatAlphaDies) {
// add_out() doesn't handle floating alpha for intergal inputs
TensorFactory<ScalarType::Int> tf;
const std::vector<int32_t> sizes = {2, 2};
// Destination for the op.
Tensor out = tf.zeros(sizes);
// Elementwise add operation on two integral tensor with floating alpha
// should cause an assertion and kill the test process.
ET_EXPECT_KERNEL_FAILURE(
context_,
op_masked_fill_scalar_out(
tf.ones(sizes), tf.ones(sizes), /*alpha=*/.7, out));
}
TEST_F(OpMaskedFillTest, FloatTensors) {
test_floating_point_masked_fill_scalar_out<ScalarType::Float>();
}
TEST_F(OpMaskedFillTest, DoubleTensors) {
test_floating_point_masked_fill_scalar_out<ScalarType::Double>();
}
TEST_F(OpMaskedFillTest, BoolTensors) {
TensorFactory<ScalarType::Bool> tf;
const std::vector<int32_t> sizes = {2, 2};
// Input and mask
Tensor self = tf.make(sizes, /*data=*/{false, true, false, true});
Tensor mask = tf.make(sizes, /*data=*/{true, false, true, false});
// Destination for the masked_fill.
Tensor out = tf.zeros(sizes);
op_masked_fill_scalar_out(self, mask, /*value=*/true, out);
// Check that it matches the expected output.
EXPECT_TENSOR_CLOSE(out, tf.ones(sizes));
}
// The input tensor and value may not have different dtypes.
TEST_F(OpMaskedFillTest, MismatchedInputAndValueDtypesDies) {
TensorFactory<ScalarType::Byte> tf_byte;
TensorFactory<ScalarType::Char> tf_char;
const std::vector<int32_t> sizes = {2, 2};
// Dummy input and mask value
Tensor self = tf_byte.ones(sizes);
Tensor mask = tf_char.ones(sizes);
// Destination for the fill; matches the type of the input.
Tensor out = tf_byte.zeros(sizes);
// Filling tensor with mismatched scalar should cause an assertion and kill
// the test process.
ET_EXPECT_KERNEL_FAILURE(
context_, op_masked_fill_scalar_out(self, mask, /*value=*/1.3, out));
}
// The output tensor may not have a dtype different from the inputs even if it
// has the same shape.
TEST_F(OpMaskedFillTest, MismatchedOutputDtypeDies) {
// Two different dtypes. This test uses two types with the same size to
// demonstrate that the ScalarType itself matters, not the size of the
// tensor elements.
TensorFactory<ScalarType::Bool> tf_bool;
TensorFactory<ScalarType::Byte> tf_byte;
TensorFactory<ScalarType::Char> tf_char;
const std::vector<int32_t> sizes = {2, 2};
// Input and mask
Tensor self = tf_byte.ones(sizes);
Tensor mask = tf_bool.ones(sizes);
// Destination with a dtype then input.
Tensor out = tf_char.zeros(sizes);
// Filling the tensor into a mismatched output should cause an assertion and
// kill the test process.
ET_EXPECT_KERNEL_FAILURE(
context_, op_masked_fill_scalar_out(self, mask, /*fill=*/0, out));
}
// The mask tensor type must be bool, even if shapes are the same
TEST_F(OpMaskedFillTest, MismatchedMaskDtypeDies) {
TensorFactory<ScalarType::Int> tf;
const std::vector<int32_t> sizes = {2, 2};
// Input and destination
Tensor self = tf.ones(sizes);
Tensor out = tf.zeros(sizes);
// Mask tensor with non bool dtype
Tensor mask = tf.ones(sizes);
// Filling the tensor using non boolean mask should cause an assertion and
// kill the test process.
ET_EXPECT_KERNEL_FAILURE(
context_, op_masked_fill_scalar_out(self, mask, /*fill=*/0, out));
}
// Mismatched shape tests.
TEST_F(OpMaskedFillTest, MismatchedInputShapesDies) {
TensorFactory<ScalarType::Int> tf;
TensorFactory<ScalarType::Bool> tf_bool;
// Input and mask of different shapes that cannot be broadcasted.
Tensor self = tf.ones(/*sizes=*/{4});
Tensor mask = tf_bool.ones(/*sizes=*/{2});
// Destination for the sum; matches the shape of one of the inputs.
Tensor out = tf.zeros(/*sizes=*/{4});
// Masked fill with mismatch input and mask shapes should cause an assertion
// and kill the test process.
ET_EXPECT_KERNEL_FAILURE(
context_, op_masked_fill_scalar_out(self, mask, /*value=*/0, out));
}
TEST_F(OpMaskedFillTest, BroadcastTest) {
TensorFactory<ScalarType::Int> tf;
TensorFactory<ScalarType::Bool> tf_bool;
// Input and mask of different shapes
Tensor self = tf.make({2, 2}, /*data=*/{1, 2, 4, 8});
Tensor mask = tf_bool.make({2}, /*data=*/{true, false});
// Destination for the masked_fill.
Tensor out = tf.zeros({2, 2});
// Masked fill half of the tensor.
op_masked_fill_scalar_out(
self,
mask,
/*value=*/3,
out);
// Check that it matches the expected output.
EXPECT_TENSOR_CLOSE(out, tf.make({2, 2}, /*data=*/{3, 2, 3, 8}));
}
TEST_F(OpMaskedFillTest, MismatchedOutputShapesDies) {
if (torch::executor::testing::SupportedFeatures::get()->is_aten) {
GTEST_SKIP() << "ATen kernel can handle mismatched output shape";
}
TensorFactory<ScalarType::Int> tf;
TensorFactory<ScalarType::Bool> tf_bool;
const std::vector<int32_t> sizes = {2, 2};
// Input and mask of different shapes
Tensor a = tf.ones(sizes);
Tensor b = tf_bool.ones(sizes);
// Destination with a different shape.
Tensor out = tf.zeros(/*sizes=*/{4});
// Mask filling the tensor into a mismatched output should cause an assertion
// and kill the test process.
ET_EXPECT_KERNEL_FAILURE(
context_, op_masked_fill_scalar_out(a, b, /*value=*/0, out));
}
TEST_F(OpMaskedFillTest, BroadcastDimSizeIsOneAB) {
TensorFactory<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> bool_tf;
Tensor x = tf.make(
{3, 2},
{0.9701170325279236,
0.4185227155685425,
0.39851099252700806,
0.8725584745407104,
0.714692234992981,
0.3167606592178345});
Tensor y = bool_tf.make({1, 2}, {false, false});
Tensor expected_result = tf.make(
{3, 2},
{0.9701170325279236,
0.4185227155685425,
0.39851099252700806,
0.8725584745407104,
0.714692234992981,
0.3167606592178345});
Tensor out = tf.zeros({3, 2});
Tensor ret = op_masked_fill_scalar_out(x, y, Scalar(3.0), out);
EXPECT_TENSOR_CLOSE(out, expected_result);
}
TEST_F(OpMaskedFillTest, BroadcastDimSizeMissingAB) {
TensorFactory<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> bool_tf;
Tensor x = tf.make(
{3, 2},
{0.9701170325279236,
0.4185227155685425,
0.39851099252700806,
0.8725584745407104,
0.714692234992981,
0.3167606592178345});
Tensor y = bool_tf.make({2}, {false, false});
Tensor expected_result = tf.make(
{3, 2},
{0.9701170325279236,
0.4185227155685425,
0.39851099252700806,
0.8725584745407104,
0.714692234992981,
0.3167606592178345});
Tensor out = tf.zeros({3, 2});
Tensor ret = op_masked_fill_scalar_out(x, y, Scalar(3.0), out);
EXPECT_TENSOR_CLOSE(out, expected_result);
}
TEST_F(OpMaskedFillTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> bool_tf;
Tensor x = tf.make(
{3, 2},
{0.974706768989563,
0.46383917331695557,
0.050839245319366455,
0.26296138763427734,
0.8404526114463806,
0.49675875902175903});
Tensor y = bool_tf.make({3, 2}, {false, false, false, false, false, true});
Tensor expected_result = tf.make(
{3, 2},
{0.974706768989563,
0.46383917331695557,
0.050839245319366455,
0.26296138763427734,
0.8404526114463806,
3.0});
Tensor out =
tf.zeros({3, 2}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
Tensor ret = op_masked_fill_scalar_out(x, y, Scalar(3.0), out);
EXPECT_TENSOR_CLOSE(out, expected_result);
}
TEST_F(OpMaskedFillTest, DynamicShapeUpperBoundLargerThanExpected) {
TensorFactory<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> bool_tf;
Tensor x = tf.make(
{3, 2},
{0.974706768989563,
0.46383917331695557,
0.050839245319366455,
0.26296138763427734,
0.8404526114463806,
0.49675875902175903});
Tensor y = bool_tf.make({3, 2}, {false, false, false, false, false, true});
Tensor expected_result = tf.make(
{3, 2},
{0.974706768989563,
0.46383917331695557,
0.050839245319366455,
0.26296138763427734,
0.8404526114463806,
3.0});
Tensor out =
tf.zeros({6, 4}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
Tensor ret = op_masked_fill_scalar_out(x, y, Scalar(3.0), out);
EXPECT_TENSOR_CLOSE(out, expected_result);
}
TEST_F(OpMaskedFillTest, DynamicShapeUnbound) {
GTEST_SKIP() << "Dynamic shape unbound not supported";
TensorFactory<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> bool_tf;
Tensor x = tf.make(
{3, 2},
{0.974706768989563,
0.46383917331695557,
0.050839245319366455,
0.26296138763427734,
0.8404526114463806,
0.49675875902175903});
Tensor y = bool_tf.make({3, 2}, {false, false, false, false, false, true});
Tensor expected_result = tf.make(
{3, 2},
{0.974706768989563,
0.46383917331695557,
0.050839245319366455,
0.26296138763427734,
0.8404526114463806,
3.0});
Tensor out =
tf.zeros({1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND);
Tensor ret = op_masked_fill_scalar_out(x, y, Scalar(3.0), out);
EXPECT_TENSOR_CLOSE(out, expected_result);
}
TEST_F(OpMaskedFillTest, BroadcastDimSizeIsOneBA) {
TensorFactory<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> tf_bool;
auto x = tf.make(
{3, 2},
{0.38566190004348755,
0.47776442766189575,
0.1954779028892517,
0.6691004633903503,
0.6580829620361328,
0.48968571424484253});
auto y = tf_bool.make({2}, {false, false});
auto z = Scalar(3.0);
Tensor expected_result = tf.make(
{3, 2},
{0.38566190004348755,
0.47776442766189575,
0.1954779028892517,
0.6691004633903503,
0.6580829620361328,
0.48968571424484253});
Tensor out = tf.zeros({3, 2});
Tensor ret = op_masked_fill_scalar_out(x, y, z, out);
EXPECT_TENSOR_CLOSE(out, expected_result);
}
TEST_F(OpMaskedFillTest, BroadcastDimSizeMissingBA) {
TensorFactory<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> tf_bool;
auto x = tf.make(
{3, 2},
{0.38566190004348755,
0.47776442766189575,
0.1954779028892517,
0.6691004633903503,
0.6580829620361328,
0.48968571424484253});
auto y = tf_bool.make({2}, {false, false});
auto z = Scalar(3.0);
Tensor expected_result = tf.make(
{3, 2},
{0.38566190004348755,
0.47776442766189575,
0.1954779028892517,
0.6691004633903503,
0.6580829620361328,
0.48968571424484253});
Tensor out = tf.zeros({3, 2});
Tensor ret = op_masked_fill_scalar_out(x, y, z, out);
EXPECT_TENSOR_CLOSE(out, expected_result);
}