Files
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

203 lines
6.5 KiB
C++

/* 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 <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>
#include "flatbuffers/flexbuffers.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace ops {
namespace custom {
namespace aeq_hadamard_rotation {
static const int kInputTensor = 0;
static const int kOutputTensor = 0;
struct OpData {
bool is_initialized = false;
int hadamard_size = 0;
std::vector<int> random_binary_vector;
};
// FWHT implementation for fixed bounds.
//
// The compiler is capable of fully unrolling this, which gives much better
// performance.
template <size_t N, size_t H>
void FWHTStaticSize(float* data) {
for (size_t h = H; h < N; h *= 2) {
for (size_t i = 0; i < N; i += 2 * h) {
for (size_t j = i; j < i + h; ++j) {
const float x = data[j];
const float y = data[j + h];
data[j] = x + y;
data[j + h] = x - y;
}
}
}
}
// Fast Walsh Hadamard Transform. Updates `data` in place.
template <size_t kUnrollThreshold = 64>
void FWHTFast(float* data, int hadamard_size) {
if ((hadamard_size & (hadamard_size - 1)) != 0) {
std::cerr << "hadamard_size needs to be a power of 2\n";
return;
}
if (hadamard_size < kUnrollThreshold) {
// For small sizes, we run an "unoptimized" loop. This avoids unrolling the
// loops for every valid size under kUnrollSize.
for (size_t h = 1; h < hadamard_size; h *= 2) {
for (size_t i = 0; i < hadamard_size; i += 2 * h) {
for (size_t j = i; j < i + h; ++j) {
const float x = data[j];
const float y = data[j + h];
data[j] = x + y;
data[j + h] = x - y;
}
}
}
} else {
const int num_chunks = hadamard_size / kUnrollThreshold;
float* in = data;
// Use general, iterative loops algorithm for sizes up to kUnrollLimit.
for (int chunk = 0; chunk < num_chunks; ++chunk, in += kUnrollThreshold) {
FWHTStaticSize<kUnrollThreshold, 1>(in);
}
// Finish the bigger butterflies manually.
for (int chunk_size = kUnrollThreshold; chunk_size < hadamard_size;
chunk_size *= 2) {
float* in1 = &data[0];
float* in2 = &data[chunk_size];
for (int i = 0; i < hadamard_size;
i += chunk_size * 2, in1 += chunk_size, in2 += chunk_size) {
for (int j = i; j < i + chunk_size; j += kUnrollThreshold) {
// Compiler will unroll this fixed size loop easily.
for (int k = 0; k < kUnrollThreshold; k++) {
float x = *in1;
float y = *in2;
*in1++ = x + y;
*in2++ = x - y;
}
}
}
}
}
// Calculate the inverse square root once.
const float norm_factor = 1.0f / std::sqrt(hadamard_size);
for (int i = 0; i < hadamard_size; ++i) {
data[i] *= norm_factor;
}
}
void* Init(TfLiteContext* context, const char* buffer, size_t length) {
OpData* op_data = new OpData();
op_data->is_initialized = false;
return op_data;
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, NumInputs(node), 1);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
if (!op_data->is_initialized) {
const uint8_t* buffer =
reinterpret_cast<const uint8_t*>(node->custom_initial_data);
const size_t length = node->custom_initial_data_size;
auto flexbuffer_map = flexbuffers::GetRoot(buffer, length).AsMap();
int32_t hadamard_size = flexbuffer_map["hadamard_size"].AsInt32();
std::vector<int> vec;
const auto& vector = flexbuffer_map["random_binary_vector"].AsVector();
vec.reserve(vector.size());
for (size_t i = 0; i < vector.size(); i++) {
vec.push_back(vector[i].AsInt8());
}
op_data->hadamard_size = hadamard_size;
op_data->random_binary_vector = vec;
op_data->is_initialized = true;
}
// Prepare the inputs.
const TfLiteTensor* input_tensor;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kInputTensor, &input_tensor));
TF_LITE_ENSURE(context, input_tensor->type == kTfLiteFloat32 ||
input_tensor->type == kTfLiteInt32);
return kTfLiteOk;
}
void Free(TfLiteContext* context, void* buffer) {
delete static_cast<OpData*>(buffer);
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
const TfLiteTensor* input_tensor;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kInputTensor, &input_tensor));
TfLiteTensor* output;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kOutputTensor, &output));
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
int hadamard_size = op_data->hadamard_size;
int input_batch = 1;
int input_features = input_tensor->dims->data[0];
int input_feature_size = input_tensor->dims->data[1];
if (input_tensor->dims->size == 3) {
input_batch = input_tensor->dims->data[0];
input_features = input_tensor->dims->data[1];
input_feature_size = input_tensor->dims->data[2];
}
memcpy(output->data.f, input_tensor->data.f, input_tensor->bytes);
const int num_hadamards_per_feature = input_feature_size / hadamard_size;
const int total_transforms =
input_batch * input_features * num_hadamards_per_feature;
for (int i = 0; i < total_transforms; ++i) {
int chunk_start = i * hadamard_size;
// Update output->data.f in place.
FWHTFast(&output->data.f[chunk_start], hadamard_size);
}
return kTfLiteOk;
}
} // namespace aeq_hadamard_rotation
TfLiteRegistration* Register_HADAMARD_ROTATION() {
static TfLiteRegistration r = {
aeq_hadamard_rotation::Init, aeq_hadamard_rotation::Free,
aeq_hadamard_rotation::Prepare, aeq_hadamard_rotation::Eval};
return &r;
}
} // namespace custom
} // namespace ops
} // namespace tflite