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

493 lines
21 KiB
C++

/* Copyright 2026 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 "tensorflow/lite/delegates/ynnpack/elementwise.h"
#include <cstdint>
#include <vector>
#include "ynnpack/composites/composites.h" // from @XNNPACK
#include "ynnpack/include/ynnpack.h" // from @XNNPACK
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/ynnpack/utils.h"
namespace tflite {
namespace ynnpack {
// ==============================================================================
// Unary Operations
// ==============================================================================
TfLiteStatus IsUnaryOpSupported(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) {
TF_LITE_ENSURE_EQ(context, node->inputs->size, 1);
TF_LITE_ENSURE_EQ(context, node->outputs->size, 1);
const TfLiteTensor& input = context->tensors[node->inputs->data[0]];
const TfLiteTensor& output = context->tensors[node->outputs->data[0]];
TF_LITE_ENSURE(context, IsTensorSupported(input));
TF_LITE_ENSURE(context, IsTensorSupported(output));
TF_LITE_ENSURE(context, YnnTypeElementCount(GetYnnType(input.type)) == 1);
TF_LITE_ENSURE(context, YnnTypeElementCount(GetYnnType(output.type)) == 1);
int builtin_code = registration->builtin_code;
ynn_unary_operator op = GetYnnUnaryOperator(builtin_code);
bool is_decomposed = (builtin_code == kTfLiteBuiltinGelu ||
builtin_code == kTfLiteBuiltinElu ||
builtin_code == kTfLiteBuiltinLeakyRelu ||
builtin_code == kTfLiteBuiltinHardSwish ||
builtin_code == kTfLiteBuiltinRelu ||
builtin_code == kTfLiteBuiltinRelu6 ||
builtin_code == kTfLiteBuiltinReluN1To1 ||
builtin_code == kTfLiteBuiltinRelu0To1);
TF_LITE_ENSURE(context, op != ynn_unary_invalid || is_decomposed);
if (op != ynn_unary_convert) {
TF_LITE_ENSURE_EQ(context, input.type, output.type);
}
if (op == ynn_unary_convert) {
// Reject constant inputs to allow TFLite caching optimization.
TF_LITE_ENSURE_MSG(context, input.allocation_type != kTfLiteMmapRo,
"Constant input for convert is not supported");
// YNNPACK convert to integer uses rounding, but TFLite Cast expects
// truncation. Reject all float-to-integer conversions.
if ((input.type == kTfLiteFloat32 || input.type == kTfLiteFloat16) &&
(output.type == kTfLiteInt32 || output.type == kTfLiteInt8 ||
output.type == kTfLiteUInt8 || output.type == kTfLiteInt16)) {
TF_LITE_ENSURE_MSG(context, false,
"Float to integer conversion is not supported");
}
}
if (op == ynn_unary_square_root || op == ynn_unary_reciprocal_square_root) {
// Reject quantized Sqrt/Rsqrt because we cannot report errors for negative
// inputs during execution if we delegate them.
TF_LITE_ENSURE_MSG(context,
input.quantization.type == kTfLiteNoQuantization,
"Quantized Sqrt/Rsqrt is not supported");
}
// Check for fused activation.
TfLiteFusedActivation activation = GetFusedActivation(registration, node);
TF_LITE_ENSURE(context, IsActivationSupported(activation, output.type));
return kTfLiteOk;
}
TfLiteStatus DefineUnaryNode(TfLiteContext* context, ynn_subgraph_t subgraph,
TensorToValueIdMap& tensor_to_value_id,
const NodeInfo& node) {
return DefineDecomposedUnaryNode(
context, subgraph, tensor_to_value_id, node,
[context, subgraph, &node](uint32_t input_id,
uint32_t& output_id) -> TfLiteStatus {
ynn_unary_operator op = GetYnnUnaryOperator(node.builtin_code);
if (op != ynn_unary_invalid) {
TF_LITE_ENSURE_YNN_STATUS(
ynn_define_unary(subgraph, op, input_id, &output_id, 0));
return kTfLiteOk;
}
switch (node.builtin_code) {
case kTfLiteBuiltinGelu: {
TfLiteNode* tflite_node;
TfLiteRegistration* reg;
TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration(
context, node.node_index, &tflite_node, &reg));
const auto* params =
static_cast<const TfLiteGeluParams*>(tflite_node->builtin_data);
bool approximate = params && params->approximate;
if (approximate) {
TF_LITE_ENSURE_YNN_STATUS(
ynn::define_approx_gelu(subgraph, input_id, output_id));
} else {
TF_LITE_ENSURE_YNN_STATUS(
ynn::define_gelu(subgraph, input_id, output_id));
}
return kTfLiteOk;
}
case kTfLiteBuiltinElu:
TF_LITE_ENSURE_YNN_STATUS(
ynn::define_elu(subgraph, input_id, 1.0f, output_id));
return kTfLiteOk;
case kTfLiteBuiltinLeakyRelu: {
TfLiteNode* tflite_node;
TfLiteRegistration* reg;
TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration(
context, node.node_index, &tflite_node, &reg));
const auto* params = static_cast<const TfLiteLeakyReluParams*>(
tflite_node->builtin_data);
float alpha = params ? params->alpha : 0.2f;
TF_LITE_ENSURE_YNN_STATUS(
ynn::define_leaky_relu(subgraph, input_id, alpha, output_id));
return kTfLiteOk;
}
case kTfLiteBuiltinHardSwish:
TF_LITE_ENSURE_YNN_STATUS(
ynn::define_hardswish(subgraph, input_id, output_id));
return kTfLiteOk;
case kTfLiteBuiltinRelu0To1:
return ApplyClamp(context, subgraph, 0.0, 1.0, input_id, output_id,
node.outputs[0], ynn_type_fp32);
case kTfLiteBuiltinRelu:
case kTfLiteBuiltinRelu6:
case kTfLiteBuiltinReluN1To1: {
TfLiteFusedActivation activation = kTfLiteActNone;
if (node.builtin_code == kTfLiteBuiltinRelu) {
activation = kTfLiteActRelu;
} else if (node.builtin_code == kTfLiteBuiltinRelu6) {
activation = kTfLiteActRelu6;
} else if (node.builtin_code == kTfLiteBuiltinReluN1To1) {
activation = kTfLiteActReluN1To1;
}
return ApplyActivation(context, subgraph, activation, input_id,
output_id, node.outputs[0], ynn_type_fp32);
}
default:
TF_LITE_ENSURE(context, false);
}
});
}
// ==============================================================================
// Binary Operations
// ==============================================================================
TfLiteStatus IsBinaryOpSupported(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) {
TF_LITE_ENSURE_EQ(context, node->inputs->size, 2);
TF_LITE_ENSURE_EQ(context, node->outputs->size, 1);
const TfLiteTensor& input1 = context->tensors[node->inputs->data[0]];
const TfLiteTensor& input2 = context->tensors[node->inputs->data[1]];
const TfLiteTensor& output = context->tensors[node->outputs->data[0]];
TF_LITE_ENSURE(context, IsTensorSupported(input1));
TF_LITE_ENSURE(context, IsTensorSupported(input2));
TF_LITE_ENSURE(context, IsTensorSupported(output));
TF_LITE_ENSURE(context, YnnTypeElementCount(GetYnnType(input1.type)) == 1);
TF_LITE_ENSURE(context, YnnTypeElementCount(GetYnnType(input2.type)) == 1);
TF_LITE_ENSURE(context, YnnTypeElementCount(GetYnnType(output.type)) == 1);
// YNNPACK division is floating point division only. Integer and quantized
// division are not supported.
if ((registration->builtin_code == kTfLiteBuiltinDiv ||
registration->builtin_code == kTfLiteBuiltinStablehloDivide) &&
output.type != kTfLiteFloat32 && output.type != kTfLiteFloat16 &&
output.type != kTfLiteBFloat16) {
TF_LITE_ENSURE_MSG(
context, false,
"Non-floating point division is not supported by YNNPACK");
}
// Check for fused activation.
TfLiteFusedActivation activation = GetFusedActivation(registration, node);
TF_LITE_ENSURE(context, IsActivationSupported(activation, output.type));
return kTfLiteOk;
}
TfLiteStatus DefineBinaryNode(TfLiteContext* context, ynn_subgraph_t subgraph,
TensorToValueIdMap& tensor_to_value_id,
const NodeInfo& node) {
TF_LITE_ENSURE_EQ(context, node.inputs.size(), 2);
TF_LITE_ENSURE_EQ(context, node.outputs.size(), 1);
int input1_tensor_index = node.inputs[0];
int input2_tensor_index = node.inputs[1];
int output_tensor_index = node.outputs[0];
uint32_t input1_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, input1_tensor_index);
uint32_t input2_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, input2_tensor_index);
uint32_t output_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, output_tensor_index);
TF_LITE_ENSURE(context, input1_val_id != YNN_INVALID_VALUE_ID);
TF_LITE_ENSURE(context, input2_val_id != YNN_INVALID_VALUE_ID);
TF_LITE_ENSURE(context, output_val_id != YNN_INVALID_VALUE_ID);
uint32_t float_input1_val_id = YNN_INVALID_VALUE_ID;
TF_LITE_ENSURE_STATUS(DequantizeIfNeeded(
context, subgraph, tensor_to_value_id, input1_tensor_index, input1_val_id,
&float_input1_val_id));
uint32_t float_input2_val_id = YNN_INVALID_VALUE_ID;
TF_LITE_ENSURE_STATUS(DequantizeIfNeeded(
context, subgraph, tensor_to_value_id, input2_tensor_index, input2_val_id,
&float_input2_val_id));
const TfLiteTensor& output_tensor = context->tensors[output_tensor_index];
ynn_type internal_type = IsQuantized(output_tensor)
? ynn_type_fp32
: GetYnnType(output_tensor.type);
TfLiteFusedActivation activation = node.activation;
bool is_output_quantized = IsQuantized(output_tensor);
uint32_t float_output_val_id =
!is_output_quantized && activation == kTfLiteActNone
? output_val_id
: YNN_INVALID_VALUE_ID;
uint32_t broadcasted_input1_val_id = float_input1_val_id;
uint32_t broadcasted_input2_val_id = float_input2_val_id;
const TfLiteTensor& input1_tensor = context->tensors[input1_tensor_index];
const TfLiteTensor& input2_tensor = context->tensors[input2_tensor_index];
int rank1 = input1_tensor.dims->size;
int rank2 = input2_tensor.dims->size;
if (!IsStablehloOp(node.builtin_code)) {
// If the op is not a StableHLO op, we might need to broadcast.
TF_LITE_ENSURE_STATUS(ImplementMutualBroadcasting(
context, subgraph, rank1, rank2, 0, 0, broadcasted_input1_val_id,
broadcasted_input2_val_id));
}
TF_LITE_ENSURE_YNN_STATUS(
ynn_define_binary(subgraph, GetYnnBinaryOperator(node.builtin_code),
broadcasted_input1_val_id, broadcasted_input2_val_id,
&float_output_val_id, 0));
uint32_t active_output_val_id = float_output_val_id;
if (activation != kTfLiteActNone) {
uint32_t activation_output_val_id =
is_output_quantized ? YNN_INVALID_VALUE_ID : output_val_id;
TF_LITE_ENSURE_STATUS(ApplyActivation(
context, subgraph, activation, float_output_val_id,
activation_output_val_id, output_tensor_index, internal_type));
active_output_val_id = activation_output_val_id;
}
if (is_output_quantized) {
TF_LITE_ENSURE_STATUS(Quantize(context, subgraph, tensor_to_value_id,
output_tensor_index, active_output_val_id,
output_val_id));
}
return kTfLiteOk;
}
// ==============================================================================
// Ternary Operations (Clamp)
// ==============================================================================
TfLiteStatus IsStablehloClampSupported(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) {
TF_LITE_ENSURE_EQ(context, node->inputs->size, 3);
TF_LITE_ENSURE_EQ(context, node->outputs->size, 1);
const TfLiteTensor& min_tensor = context->tensors[node->inputs->data[0]];
const TfLiteTensor& operand_tensor = context->tensors[node->inputs->data[1]];
const TfLiteTensor& max_tensor = context->tensors[node->inputs->data[2]];
const TfLiteTensor& output = context->tensors[node->outputs->data[0]];
TF_LITE_ENSURE(context, IsTensorSupported(min_tensor));
TF_LITE_ENSURE(context, IsTensorSupported(operand_tensor));
TF_LITE_ENSURE(context, IsTensorSupported(max_tensor));
TF_LITE_ENSURE(context, IsTensorSupported(output));
ynn_type operand_ynn_type = GetYnnType(operand_tensor.type);
switch (operand_ynn_type) {
case ynn_type_int8:
case ynn_type_uint8:
case ynn_type_int32:
case ynn_type_fp16:
case ynn_type_bf16:
return kTfLiteError;
default:
break;
}
TF_LITE_ENSURE_EQ(context, min_tensor.type, output.type);
TF_LITE_ENSURE_EQ(context, operand_tensor.type, output.type);
TF_LITE_ENSURE_EQ(context, max_tensor.type, output.type);
// Fused activation is not supported for StableHLO Clamp.
TfLiteFusedActivation activation = GetFusedActivation(registration, node);
TF_LITE_ENSURE_EQ(context, activation, kTfLiteActNone);
return kTfLiteOk;
}
TfLiteStatus DefineStablehloClampNode(TfLiteContext* context,
ynn_subgraph_t subgraph,
TensorToValueIdMap& tensor_to_value_id,
const NodeInfo& node) {
TF_LITE_ENSURE_EQ(context, node.inputs.size(), 3);
TF_LITE_ENSURE_EQ(context, node.outputs.size(), 1);
int min_tensor_index = node.inputs[0];
int operand_tensor_index = node.inputs[1];
int max_tensor_index = node.inputs[2];
int output_tensor_index = node.outputs[0];
uint32_t min_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, min_tensor_index);
uint32_t operand_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, operand_tensor_index);
uint32_t max_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, max_tensor_index);
uint32_t output_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, output_tensor_index);
TF_LITE_ENSURE(context, min_val_id != YNN_INVALID_VALUE_ID);
TF_LITE_ENSURE(context, operand_val_id != YNN_INVALID_VALUE_ID);
TF_LITE_ENSURE(context, max_val_id != YNN_INVALID_VALUE_ID);
TF_LITE_ENSURE(context, output_val_id != YNN_INVALID_VALUE_ID);
uint32_t float_min_val_id = YNN_INVALID_VALUE_ID;
TF_LITE_ENSURE_STATUS(DequantizeIfNeeded(context, subgraph,
tensor_to_value_id, min_tensor_index,
min_val_id, &float_min_val_id));
uint32_t float_operand_val_id = YNN_INVALID_VALUE_ID;
TF_LITE_ENSURE_STATUS(DequantizeIfNeeded(
context, subgraph, tensor_to_value_id, operand_tensor_index,
operand_val_id, &float_operand_val_id));
uint32_t float_max_val_id = YNN_INVALID_VALUE_ID;
TF_LITE_ENSURE_STATUS(DequantizeIfNeeded(context, subgraph,
tensor_to_value_id, max_tensor_index,
max_val_id, &float_max_val_id));
const TfLiteTensor& output_tensor = context->tensors[output_tensor_index];
bool is_output_quantized = IsQuantized(output_tensor);
uint32_t float_output_val_id =
!is_output_quantized ? output_val_id : YNN_INVALID_VALUE_ID;
// temp = max(operand, min)
uint32_t temp_val_id = YNN_INVALID_VALUE_ID;
TF_LITE_ENSURE_YNN_STATUS(
ynn_define_binary(subgraph, ynn_binary_max, float_operand_val_id,
float_min_val_id, &temp_val_id, 0));
// output = min(temp, max)
TF_LITE_ENSURE_YNN_STATUS(ynn_define_binary(subgraph, ynn_binary_min,
temp_val_id, float_max_val_id,
&float_output_val_id, 0));
if (is_output_quantized) {
TF_LITE_ENSURE_STATUS(Quantize(context, subgraph, tensor_to_value_id,
output_tensor_index, float_output_val_id,
output_val_id));
}
return kTfLiteOk;
}
// ==============================================================================
// Quantize/Dequantize Operations
// ==============================================================================
TfLiteStatus IsQuantizeSupported(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) {
TF_LITE_ENSURE_EQ(context, node->inputs->size, 1);
TF_LITE_ENSURE_EQ(context, node->outputs->size, 1);
const TfLiteTensor& input = context->tensors[node->inputs->data[0]];
const TfLiteTensor& output = context->tensors[node->outputs->data[0]];
TF_LITE_ENSURE(context, IsTensorSupported(input));
TF_LITE_ENSURE(context, IsTensorSupported(output));
TF_LITE_ENSURE(context, !IsQuantized(input));
TF_LITE_ENSURE(context, YnnTypeElementCount(GetYnnType(output.type)) == 1);
return kTfLiteOk;
}
TfLiteStatus DefineQuantizeNode(TfLiteContext* context, ynn_subgraph_t subgraph,
TensorToValueIdMap& tensor_to_value_id,
const NodeInfo& node) {
TF_LITE_ENSURE_EQ(context, node.inputs.size(), 1);
TF_LITE_ENSURE_EQ(context, node.outputs.size(), 1);
int input_tensor_index = node.inputs[0];
int output_tensor_index = node.outputs[0];
uint32_t input_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, input_tensor_index);
uint32_t output_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, output_tensor_index);
const TfLiteTensor& output_tensor = context->tensors[output_tensor_index];
uint32_t scale_id = YNN_INVALID_VALUE_ID;
uint32_t zp_id = YNN_INVALID_VALUE_ID;
TF_LITE_ENSURE_STATUS(DefineQuantizationParams(
context, subgraph, output_tensor, &scale_id, &zp_id));
ynn_type ynn_type = GetYnnType(output_tensor.type);
TF_LITE_ENSURE_YNN_STATUS(ynn_define_quantize(
subgraph, input_val_id, ynn_type, zp_id, scale_id, &output_val_id, 0));
return kTfLiteOk;
}
TfLiteStatus IsDequantizeSupported(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) {
TF_LITE_ENSURE_EQ(context, node->inputs->size, 1);
TF_LITE_ENSURE_EQ(context, node->outputs->size, 1);
const TfLiteTensor& input = context->tensors[node->inputs->data[0]];
const TfLiteTensor& output = context->tensors[node->outputs->data[0]];
TF_LITE_ENSURE(context, IsTensorSupported(input));
TF_LITE_ENSURE(context, IsTensorSupported(output));
TF_LITE_ENSURE(context, !IsQuantized(output));
TF_LITE_ENSURE(context, YnnTypeElementCount(GetYnnType(input.type)) == 1);
return kTfLiteOk;
}
TfLiteStatus DefineDequantizeNode(TfLiteContext* context,
ynn_subgraph_t subgraph,
TensorToValueIdMap& tensor_to_value_id,
const NodeInfo& node) {
TF_LITE_ENSURE_EQ(context, node.inputs.size(), 1);
TF_LITE_ENSURE_EQ(context, node.outputs.size(), 1);
int input_tensor_index = node.inputs[0];
int output_tensor_index = node.outputs[0];
uint32_t input_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, input_tensor_index);
uint32_t output_val_id = GetOrCreateValueId(
context, subgraph, tensor_to_value_id, output_tensor_index);
const TfLiteTensor& input_tensor = context->tensors[input_tensor_index];
uint32_t scale_id = YNN_INVALID_VALUE_ID;
uint32_t zp_id = YNN_INVALID_VALUE_ID;
TF_LITE_ENSURE_STATUS(DefineQuantizationParams(
context, subgraph, input_tensor, &scale_id, &zp_id));
TF_LITE_ENSURE_YNN_STATUS(ynn_define_dequantize(subgraph, input_val_id, zp_id,
scale_id, ynn_type_fp32,
&output_val_id, 0));
return kTfLiteOk;
}
} // namespace ynnpack
} // namespace tflite