/* Copyright 2025 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 #include #include #include #include #include #include "flatbuffers/flexbuffers.h" // from @flatbuffers #include "tensorflow/lite/core/interpreter.h" #include "tensorflow/lite/kernels/test_util.h" #include "tensorflow/lite/schema/schema_generated.h" namespace tflite { namespace ops { namespace custom { TfLiteRegistration* Register_HADAMARD_ROTATION(); namespace { using ::testing::ElementsAre; using ::testing::Values; class BaseHadamardRotationOpModel : public SingleOpModel { public: BaseHadamardRotationOpModel(const int size, const TensorData& input, const TensorData& output) { input1_ = AddInput(input); output1_ = AddOutput(output); flexbuffers::Builder fbb; fbb.Map([&]() { fbb.Int("hadamard_size", size); auto start = fbb.StartVector("random_binary_vector"); for (int i = 0; i < size; ++i) { fbb.Add(1); } fbb.EndVector(start, false, false); }); fbb.Finish(); SetCustomOp("aeq.hadamard_rotation", fbb.GetBuffer(), Register_HADAMARD_ROTATION); BuildInterpreter({GetShape(input1_)}); } int input1() { return input1_; } template void SetInput1(std::vector data) { PopulateTensor(input1_, data); } template std::vector GetOutput1() { return ExtractVector(output1_); } std::vector GetOutputShape1() { return GetTensorShape(output1_); } protected: int input1_; int output1_; }; class HadamardRotationOpTest : public ::testing::TestWithParam {}; TEST_P(HadamardRotationOpTest, BasicTest) { int size = GetParam(); // Use a batch of 2 vectors to be transformed. BaseHadamardRotationOpModel m(size, {TensorType_FLOAT32, {1, size * 2}}, {TensorType_FLOAT32, {1, size * 2}}); // All ones input vector should generate an impulse output, with the first // element being the only non-zero element with value of sqrt(size). // Here we test a batch of 2 such inputs. std::vector ones; for (int i = 0; i < size * 2; ++i) { ones.push_back(1.0); } m.SetInput1(ones); ASSERT_EQ(m.Invoke(), kTfLiteOk); std::vector output_shape1 = m.GetOutputShape1(); EXPECT_THAT(output_shape1, ElementsAre(1, size * 2)); std::vector output = m.GetOutput1(); // First element of both outputs should be sqrt(size). EXPECT_FLOAT_EQ(output[0], std::sqrt(size)); EXPECT_FLOAT_EQ(output[size], std::sqrt(size)); // All other elements should be zero. for (int i = 1; i < size; ++i) { EXPECT_FLOAT_EQ(output[i], 0.0); EXPECT_FLOAT_EQ(output[i + size], 0.0); } } // Recursive implementation of the Fast Walsh-Hadamard Transform. // Runtime is a faster, iterative version of the algorithm, but this may be // more intuitive. void recursive_FWHT(float* inout_vector, int len) { // Transform the vector in place with the FWHT algorithm of size len. float tmp = 0.0; if (len == 2) { tmp = inout_vector[0]; inout_vector[0] = (tmp + inout_vector[1]) / std::sqrt(2); inout_vector[1] = (tmp - inout_vector[1]) / std::sqrt(2); } else { int half_len = len / 2; recursive_FWHT(inout_vector, half_len); recursive_FWHT(inout_vector + half_len, half_len); for (int i = 0; i < half_len; i++) { tmp = inout_vector[i]; inout_vector[i] = (tmp + inout_vector[i + half_len]) / std::sqrt(2); inout_vector[i + half_len] = (tmp - inout_vector[i + half_len]) / std::sqrt(2); } } } TEST_P(HadamardRotationOpTest, RandomInputTest) { int size = GetParam(); // Compare simple recursive FWHT implementation with the TFLite op. // Use a batch of 2 random vectors to be transformed. BaseHadamardRotationOpModel m(size, {TensorType_FLOAT32, {1, size * 2}}, {TensorType_FLOAT32, {1, size * 2}}); std::mt19937 gen(12345); std::uniform_real_distribution<> dist(-1.0, 1.0); // Container for input vectors. std::vector randoms; // Initialize both input vectors with random numbers. for (int i = 0; i < size * 2; ++i) { randoms.push_back(dist(gen)); } m.SetInput1(randoms); ASSERT_EQ(m.Invoke(), kTfLiteOk); std::vector output_shape1 = m.GetOutputShape1(); EXPECT_THAT(output_shape1, ElementsAre(1, size * 2)); std::vector output = m.GetOutput1(); // Transform both input vectors with the recursive FWHT algorithm. recursive_FWHT(randoms.data(), size); recursive_FWHT(randoms.data() + size, size); // After transforming both vectors in-place, they should be the same as the // output of the TFLite op. for (int i = 0; i < size * 2; ++i) { EXPECT_NEAR(output[i], randoms[i], 1e-5); } } INSTANTIATE_TEST_SUITE_P(HadamardSizes, HadamardRotationOpTest, Values(4, 16, 64)); } // namespace } // namespace custom } // namespace ops } // namespace tflite