Files
tensorflow--tensorflow/tensorflow/lite/kernels/gather_test.cc
T
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

532 lines
22 KiB
C++

/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <stdint.h>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <string>
#include <type_traits>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "Eigen/Core" // from @eigen_archive
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/kernels/internal/portable_tensor_utils.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/types/half.h"
namespace tflite {
namespace {
using ::testing::ElementsAreArray;
template <typename InputType, typename PositionsType>
class GatherOpModel : public SingleOpModel {
public:
GatherOpModel(const TensorData& input, const TensorData& positions,
bool constant_tensor, const std::vector<InputType>& input_data,
const std::vector<PositionsType>& positions_data, int axis = 0,
int batch_dims = 0) {
if (constant_tensor) {
input_ = AddConstInput(input, input_data);
positions_ = AddConstInput(positions, positions_data);
} else {
input_ = AddInput(input);
positions_ = AddInput(positions);
}
output_ = AddOutput(input.type);
SetBuiltinOp(BuiltinOperator_GATHER, BuiltinOptions_GatherOptions,
CreateGatherOptions(builder_, axis, batch_dims).Union());
BuildInterpreter({GetShape(input_), GetShape(positions_)});
if (!constant_tensor) {
if (input.type == TensorType_INT4) {
SetInputInt4(input_, input_data,
std::is_same<std::string, InputType>());
} else {
SetInput(input_, input_data, std::is_same<std::string, InputType>());
}
SetPositions(positions_data);
}
}
template <typename T>
void SetInput(int input, const std::vector<T> data, std::false_type) {
PopulateTensor<T>(input, data);
}
// Overload for string inputs.
template <typename T>
void SetInput(int input, const std::vector<T> data, std::true_type) {
PopulateStringTensor(input_, data);
}
template <typename T>
void SetInputInt4(int input, const std::vector<T> data, std::false_type) {
auto non_const = *const_cast<std::vector<T>*>(&data);
std::vector<int8_t> data_int8(non_const.size());
std::copy(non_const.begin(), non_const.end(), data_int8.begin());
PopulateTensor4bit(input, 0, data_int8.data(),
data_int8.data() + data_int8.size());
}
template <typename T>
void SetInputInt4(int input, const std::vector<T> data, std::true_type) {
// Unsupported
}
void SetPositions(const std::vector<PositionsType>& data) {
PopulateTensor<PositionsType>(positions_, data);
}
std::vector<InputType> GetOutput() {
return ExtractVector<InputType>(output_);
}
std::vector<std::string> GetStringOutput() {
return ExtractVector<std::string>(output_);
}
std::vector<int8_t> GetInt4Output() {
const auto* tensor = interpreter_->tensor(output_);
const std::vector<int8_t> data_int8 = std::vector<int8_t>(
tensor->data.raw, tensor->data.raw + GetTensorSize(output_));
int num_elements = 1;
auto shape = GetTensorShape(output_);
for (int i = 0; i < shape.size(); i++) {
num_elements *= shape[i];
}
std::vector<int8_t> inflated_output(num_elements);
tensor_utils::UnpackPackedIntToInt8(data_int8.data(), num_elements,
/*bit_width=*/4,
inflated_output.data());
return inflated_output;
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
void SetRawInput(const char* data, size_t bytes) {
auto tensor = interpreter_->tensor(input_);
char* tensor_buffer = reinterpret_cast<char*>(malloc(bytes));
memcpy(tensor_buffer, data, bytes);
TfLiteTensorReset(tensor->type, tensor->name,
TfLiteIntArrayCopy(tensor->dims), tensor->params,
tensor_buffer, bytes, kTfLiteDynamic, tensor->allocation,
tensor->is_variable, tensor);
}
protected:
int input_;
int positions_;
int output_;
};
struct GatherOpTest : public testing::TestWithParam<bool> {};
INSTANTIATE_TEST_SUITE_P(ConstantTensor, GatherOpTest, testing::Bool());
TEST_P(GatherOpTest, Shuffle) {
bool constant_tensor = GetParam();
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {2, 2}},
{TensorType_INT32, {2}}, constant_tensor,
{-2.0, 0.2, 0.7, 0.8}, {1, 0});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(),
ElementsAreArray(ArrayFloatNear({0.7, 0.8, -2, 0.2})));
}
TEST_P(GatherOpTest, Test0DIndex) {
bool constant_tensor = GetParam();
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {2, 2}},
{TensorType_INT32, {}}, constant_tensor,
{-2.0, 0.2, 0.7, 0.8}, {1});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({0.7, 0.8})));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2}));
}
TEST_P(GatherOpTest, Test0DIndexWith0DResult) {
bool constant_tensor = GetParam();
// 0D tensor is special case in current TFLite. Test it once to make sure
// existing workarounds are fine with it.
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {3}},
{TensorType_INT32, {}}, constant_tensor,
{1.0, 2.0, 3.0}, {1});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({2.0})));
EXPECT_TRUE(m.GetOutputShape().empty());
}
TEST_P(GatherOpTest, Test1DInput1DIndex) {
bool constant_tensor = GetParam();
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {3}},
{TensorType_INT32, {1}}, constant_tensor,
{1.0, 3.0, 5.0}, {1});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({3.0})));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1}));
}
TEST_P(GatherOpTest, Test2DIndexWith2DResult) {
bool constant_tensor = GetParam();
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {3}},
{TensorType_INT32, {1, 2}}, constant_tensor,
{1.0, 2.0, 3.0}, {1, 0});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({2.0, 1.0})));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2}));
}
TEST_P(GatherOpTest, Duplicate) {
bool constant_tensor = GetParam();
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {1, 2, 2}},
{TensorType_INT32, {2}}, constant_tensor,
{-2.0, 0.2, 0.7, 0.8}, {0, 0});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(
m.GetOutput(),
ElementsAreArray(ArrayFloatNear({-2, 0.2, 0.7, 0.8, -2, 0.2, 0.7, 0.8})));
}
TEST_P(GatherOpTest, Slice) {
bool constant_tensor = GetParam();
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {4, 1}},
{TensorType_INT32, {2}}, constant_tensor,
{-2.0, 0.2, 0.7, 0.8}, {1, 3});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({0.2, 0.8})));
}
TEST_P(GatherOpTest, Axis1) {
bool constant_tensor = GetParam();
const int axis = 1;
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {1, 2, 3}},
{TensorType_INT32, {2}}, constant_tensor,
{1, 2, 3, 4, 5, 6}, {1, 0}, axis);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(),
ElementsAreArray(ArrayFloatNear({4, 5, 6, 1, 2, 3})));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 3}));
}
TEST_P(GatherOpTest, Axis10DIndex) {
bool constant_tensor = GetParam();
const int axis = 1;
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {1, 3, 2}},
{TensorType_INT32, {}}, constant_tensor,
{1, 2, 3, 4, 5, 6}, {1}, axis);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({3, 4})));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2}));
}
TEST_P(GatherOpTest, Axis1Slice) {
bool constant_tensor = GetParam();
const int axis = 1;
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {1, 4, 2}},
{TensorType_INT32, {2}}, constant_tensor,
{1, 2, 3, 4, 5, 6, 7, 8}, {3, 1}, axis);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({7, 8, 3, 4})));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 2}));
}
TEST_P(GatherOpTest, LastAxis) {
const int axis = -1;
bool constant_tensor = GetParam();
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {1, 2, 3}},
{TensorType_INT32, {2}}, constant_tensor,
{1, 2, 3, 4, 5, 6}, {2, 0}, axis);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({3, 1, 6, 4})));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 2}));
}
TEST_P(GatherOpTest, LastAxis0DIndex) {
bool constant_tensor = GetParam();
const int axis = -1;
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {1, 2, 3}},
{TensorType_INT32, {}}, constant_tensor,
{1, 2, 3, 4, 5, 6}, {2}, axis);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({3, 6})));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2}));
}
using TestTypes = testing::Types<int8_t, uint8_t, int16_t, int32_t, int64_t,
float, half, Eigen::bfloat16>;
template <typename T>
struct TypedGatherOpTest : public testing::Test {};
TYPED_TEST_CASE(TypedGatherOpTest, TestTypes);
TYPED_TEST(TypedGatherOpTest, Int32Indices) {
for (bool constant_tensor : {true, false}) {
TensorType tensor_type = GetTensorType<TypeParam>();
GatherOpModel<TypeParam, int32_t> m(
{tensor_type, {2, 2}}, {TensorType_INT32, {2}}, constant_tensor,
{TypeParam(13), TypeParam(120), TypeParam(14), TypeParam(15)}, {1, 0});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(),
ElementsAreArray({TypeParam(14), TypeParam(15), TypeParam(13),
TypeParam(120)}));
}
}
TYPED_TEST(TypedGatherOpTest, Int64Indices) {
for (bool constant_tensor : {true, false}) {
TensorType tensor_type = GetTensorType<TypeParam>();
GatherOpModel<TypeParam, int64_t> m(
{tensor_type, {2, 2}}, {TensorType_INT64, {2}}, constant_tensor,
{TypeParam(13), TypeParam(120), TypeParam(14), TypeParam(15)}, {1, 0});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
EXPECT_THAT(m.GetOutput(),
ElementsAreArray({TypeParam(14), TypeParam(15), TypeParam(13),
TypeParam(120)}));
}
}
TEST(GatherOpTest, SimpleString) {
GatherOpModel<std::string, int32_t> m(
{TensorType_STRING, {3}}, {TensorType_INT32, {2}},
/*constant_tensor=*/false, {"A", "B", "C"}, {0, 2});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
ASSERT_THAT(m.GetOutputShape(), ElementsAreArray({2}));
EXPECT_THAT(m.GetStringOutput(), ElementsAreArray({"A", "C"}));
}
TEST(GatherOpTest, StringIndexTruncation) {
GatherOpModel<std::string, int16_t> m({TensorType_STRING, {1}},
{TensorType_INT16, {1}},
/*constant_tensor=*/false, {"A"}, {0});
// Access the implementation details to manually corrupt the string tensor's
// buffer. We want to simulate:
// - num_strings = -65535 (which is 0xFFFF0001, truncates to 1 in int16_t)
// - indexes = {0}
// - pos = 0 < 1 check would pass in 16-bit, but should fail with our
// validation.
int32_t malformed_data[3];
malformed_data[0] = -65535; // N
malformed_data[1] = 12; // offset
malformed_data[2] = 12; // total length
m.SetRawInput(reinterpret_cast<const char*>(malformed_data),
sizeof(malformed_data));
// Invoke should fail (not kTfLiteOk)
EXPECT_NE(m.Invoke(), kTfLiteOk);
}
TEST_P(GatherOpTest, 2DIndexString) {
GatherOpModel<std::string, int32_t> m(
{TensorType_STRING, {3}}, {TensorType_INT32, {2, 3}},
/*constant_tensor=*/false, {"A", "B", "C"}, {0, 2, 1, 1, 0, 2});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
ASSERT_THAT(m.GetOutputShape(), ElementsAreArray({2, 3}));
EXPECT_THAT(m.GetStringOutput(),
ElementsAreArray({"A", "C", "B", "B", "A", "C"}));
}
TYPED_TEST(TypedGatherOpTest, BatchDims2) {
for (bool constant_tensor : {true, false}) {
TensorType tensor_type = GetTensorType<TypeParam>();
GatherOpModel<TypeParam, int32_t> m(
{tensor_type, {2, 2, 3, 5}}, {TensorType_INT32, {2, 2, 2}},
constant_tensor,
{TypeParam(0), TypeParam(1), TypeParam(2), TypeParam(3),
TypeParam(4), TypeParam(5), TypeParam(6), TypeParam(7),
TypeParam(8), TypeParam(9), TypeParam(10), TypeParam(11),
TypeParam(12), TypeParam(13), TypeParam(14), TypeParam(15),
TypeParam(16), TypeParam(17), TypeParam(18), TypeParam(19),
TypeParam(20), TypeParam(21), TypeParam(22), TypeParam(23),
TypeParam(24), TypeParam(25), TypeParam(26), TypeParam(27),
TypeParam(28), TypeParam(29), TypeParam(30), TypeParam(31),
TypeParam(32), TypeParam(33), TypeParam(34), TypeParam(35),
TypeParam(36), TypeParam(37), TypeParam(38), TypeParam(39),
TypeParam(40), TypeParam(41), TypeParam(42), TypeParam(43),
TypeParam(44), TypeParam(45), TypeParam(46), TypeParam(47),
TypeParam(48), TypeParam(49), TypeParam(50), TypeParam(51),
TypeParam(52), TypeParam(53), TypeParam(54), TypeParam(55),
TypeParam(56), TypeParam(57), TypeParam(58), TypeParam(59)},
{1, 0, 0, 1, 1, 0, 0, 1},
/*axis=*/2,
/*batch_dims=*/2);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
ASSERT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2, 5}));
EXPECT_THAT(
m.GetOutput(),
ElementsAreArray(
{TypeParam(5), TypeParam(6), TypeParam(7), TypeParam(8),
TypeParam(9), TypeParam(0), TypeParam(1), TypeParam(2),
TypeParam(3), TypeParam(4), TypeParam(15), TypeParam(16),
TypeParam(17), TypeParam(18), TypeParam(19), TypeParam(20),
TypeParam(21), TypeParam(22), TypeParam(23), TypeParam(24),
TypeParam(35), TypeParam(36), TypeParam(37), TypeParam(38),
TypeParam(39), TypeParam(30), TypeParam(31), TypeParam(32),
TypeParam(33), TypeParam(34), TypeParam(45), TypeParam(46),
TypeParam(47), TypeParam(48), TypeParam(49), TypeParam(50),
TypeParam(51), TypeParam(52), TypeParam(53), TypeParam(54)}));
}
}
TEST_P(GatherOpTest, BatchDims1) {
bool constant_tensor = GetParam();
GatherOpModel<int8_t, int32_t> m(
{TensorType_INT8, {2, 2, 3, 5}}, {TensorType_INT32, {2, 2, 2}},
constant_tensor,
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59},
{1, 0, 0, 1, 1, 0, 0, 1},
/*axis=*/2, /*batch_dims=*/1);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
ASSERT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2, 2, 5}));
EXPECT_THAT(
m.GetOutput(),
ElementsAreArray({5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 0, 1, 2, 3,
4, 5, 6, 7, 8, 9, 20, 21, 22, 23, 24, 15, 16, 17,
18, 19, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 35, 36,
37, 38, 39, 30, 31, 32, 33, 34, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 50, 51, 52, 53, 54, 45, 46, 47, 48, 49,
45, 46, 47, 48, 49, 50, 51, 52, 53, 54}));
}
TEST_P(GatherOpTest, NegativeBatchDims) {
bool constant_tensor = GetParam();
GatherOpModel<int8_t, int32_t> m(
{TensorType_INT8, {2, 2, 3, 5}}, {TensorType_INT32, {2, 2, 2}},
constant_tensor,
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59},
{1, 0, 0, 1, 1, 0, 0, 1},
/*axis=*/2, /*batch_dims=*/-2);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
ASSERT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2, 2, 5}));
EXPECT_THAT(
m.GetOutput(),
ElementsAreArray({5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 0, 1, 2, 3,
4, 5, 6, 7, 8, 9, 20, 21, 22, 23, 24, 15, 16, 17,
18, 19, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 35, 36,
37, 38, 39, 30, 31, 32, 33, 34, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 50, 51, 52, 53, 54, 45, 46, 47, 48, 49,
45, 46, 47, 48, 49, 50, 51, 52, 53, 54}));
}
TEST_P(GatherOpTest, BatchDimsEqualIndexDims) {
bool constant_tensor = GetParam();
GatherOpModel<int8_t, int32_t> m(
{TensorType_INT8, {2, 2, 2, 5}}, {TensorType_INT32, {2, 2, 2}},
constant_tensor, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39},
{1, 0, 0, 1, 1, 0, 0, 1},
/*axis=*/3, /*batch_dims=*/3);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
ASSERT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({1, 5, 10, 16, 21, 25, 30, 36}));
}
TEST_P(GatherOpTest, ErrorOnOutOfBoundsTooLarge) {
bool constant_tensor = GetParam();
if (constant_tensor) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
(GatherOpModel<float, int32_t>({TensorType_FLOAT32, {2, 2}},
{TensorType_INT32, {2}}, constant_tensor,
{
-2.f, 0.2f, //
0.7f, 0.8f //
},
{3, 1})),
"Cannot allocate tensors");
#endif
} else {
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {2, 2}},
{TensorType_INT32, {2}}, constant_tensor,
{
-2.f, 0.2f, //
0.7f, 0.8f //
},
{3, 1});
EXPECT_EQ(m.Invoke(), kTfLiteError);
}
}
TEST_P(GatherOpTest, ErrorOnOutOfBoundsNegative) {
bool constant_tensor = GetParam();
if (constant_tensor) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
(GatherOpModel<float, int32_t>({TensorType_FLOAT32, {2, 2}},
{TensorType_INT32, {2}}, constant_tensor,
{
-2.f, 0.2f, //
0.7f, 0.8f //
},
{-1, 0})),
"Cannot allocate tensors");
#endif
} else {
GatherOpModel<float, int32_t> m({TensorType_FLOAT32, {2, 2}},
{TensorType_INT32, {2}}, constant_tensor,
{
-2.f, 0.2f, //
0.7f, 0.8f //
},
{-1, 0});
ASSERT_EQ(m.Invoke(), kTfLiteError);
m.SetPositions({-1, 0});
EXPECT_EQ(m.Invoke(), kTfLiteError);
}
}
TEST(GatherOpTest, BatchDims1Int4) {
GatherOpModel<int8_t, int32_t> m(
{TensorType_INT4, {2, 2, 3, 4}}, {TensorType_INT32, {2, 2, 2}}, false,
{1, 2, 3, 4, -1, -2, -3, -4, 0, 0, 0, 0, 1, 2, 3, 4,
-1, -2, -3, -4, 0, 0, 0, 0, 4, 5, 6, 7, -5, -6, -7, -8,
0, 0, 0, 0, 4, 5, 6, 7, -5, -6, -7, -8, 0, 0, 0, 0},
{1, 0, 0, 1, 1, 0, 0, 1},
/*axis=*/2, /*batch_dims=*/1);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
ASSERT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2, 2, 4}));
EXPECT_THAT(m.GetInt4Output(),
ElementsAreArray(
{-1, -2, -3, -4, 1, 2, 3, 4, 1, 2, 3, 4, -1, -2, -3, -4,
-1, -2, -3, -4, 1, 2, 3, 4, 1, 2, 3, 4, -1, -2, -3, -4,
-5, -6, -7, -8, 4, 5, 6, 7, 4, 5, 6, 7, -5, -6, -7, -8,
-5, -6, -7, -8, 4, 5, 6, 7, 4, 5, 6, 7, -5, -6, -7, -8}));
}
} // namespace
} // namespace tflite