Files
tensorflow--tensorflow/tensorflow/lite/kernels/while.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

492 lines
20 KiB
C++

/* Copyright 2019 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 <stddef.h>
#include <cstring>
#include <vector>
#include "tensorflow/lite/context_util.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/kernels/control_flow_common.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace ops {
namespace builtin {
namespace while_kernel {
struct OpData {
int cond_subgraph_index;
int body_subgraph_index;
bool cond_has_dynamic_output_tensors;
bool body_has_dynamic_output_tensors;
// set when Prepare_impl() is called.
bool subgraphs_prepared;
};
namespace {
TfLiteStatus CheckCondOutput(TfLiteContext* context,
const TfLiteTensor* cond_output) {
// The condition output must be a single boolean value.
TF_LITE_ENSURE_TYPES_EQ(context, cond_output->type, kTfLiteBool);
if (cond_output->dims->size == 0) {
// It's okay if it's a 0D scalar.
return kTfLiteOk;
}
// Otherwise it must be 1D with shape [1].
TF_LITE_ENSURE_EQ(context, cond_output->dims->size, 1);
TF_LITE_ENSURE_EQ(context, cond_output->dims->data[0], 1);
return kTfLiteOk;
}
} // namespace
void* Init(TfLiteContext* context, const char* buffer, size_t length) {
auto* op_data = new OpData;
const auto* params = reinterpret_cast<const TfLiteWhileParams*>(buffer);
op_data->cond_subgraph_index = params->cond_subgraph_index;
op_data->body_subgraph_index = params->body_subgraph_index;
op_data->cond_has_dynamic_output_tensors = false;
op_data->body_has_dynamic_output_tensors = false;
op_data->subgraphs_prepared = false;
return op_data;
}
void Free(TfLiteContext* context, void* buffer) {
delete reinterpret_cast<OpData*>(buffer);
}
TfLiteStatus Prepare_impl(TfLiteContext* context, TfLiteNode* node) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
int num_inputs = node->inputs->size;
// The number of outputs should be the same as number of inputs.
TF_LITE_ENSURE_EQ(context, node->outputs->size, num_inputs);
// Check subgraph indices and get subgraphs.
Subgraph* this_subgraph = reinterpret_cast<Subgraph*>(context->impl_);
auto* subgraphs = this_subgraph->GetSubgraphs();
TF_LITE_ENSURE(context, op_data->cond_subgraph_index < subgraphs->size());
TF_LITE_ENSURE(context, op_data->body_subgraph_index < subgraphs->size());
TF_LITE_ENSURE(context,
op_data->cond_subgraph_index != op_data->body_subgraph_index);
Subgraph* cond_subgraph = (*subgraphs)[op_data->cond_subgraph_index].get();
Subgraph* body_subgraph = (*subgraphs)[op_data->body_subgraph_index].get();
// Check input & output count of the condition subgraph.
TF_LITE_ENSURE_EQ(context, cond_subgraph->inputs().size(), num_inputs);
TF_LITE_ENSURE_EQ(context, cond_subgraph->outputs().size(), 1);
// Check input & output count of the body subgraph.
TF_LITE_ENSURE_EQ(context, body_subgraph->inputs().size(), num_inputs);
TF_LITE_ENSURE_EQ(context, body_subgraph->outputs().size(), num_inputs);
// Remove unused inputs of the condition subgraph to skip copying unnecessary
// inputs.
cond_subgraph->RemoveUnusedInputs();
// Prepare and check the condition subgraph.
TF_LITE_ENSURE_OK(
context, CopyTensorsShapeAndType(
context, this_subgraph, TfLiteIntArrayView(node->inputs),
cond_subgraph, cond_subgraph->inputs(), true));
TF_LITE_ENSURE_OK(context, cond_subgraph->AllocateTensors());
TfLiteTensor* cond_output =
cond_subgraph->tensor(cond_subgraph->outputs()[0]);
// This should rarely happens. In most cases the output is static with shape
// [1]. However theoretically intermediate tensors in the cond subgraph
// can be dynamic.
if (IsDynamicTensor(cond_output)) {
op_data->cond_has_dynamic_output_tensors = true;
} else {
TF_LITE_ENSURE_STATUS(CheckCondOutput(context, cond_output));
}
// Prepare and check the body subgraph.
TF_LITE_ENSURE_OK(
context, CopyTensorsShapeAndType(
context, this_subgraph, TfLiteIntArrayView(node->inputs),
body_subgraph, body_subgraph->inputs(), true));
// Detect when a WHILE input is read only.
const std::vector<int> input_tensors_count =
this_subgraph->GetInputTensorsCount();
for (int i = 0; i < num_inputs; ++i) {
if (body_subgraph->inputs()[i] == body_subgraph->outputs()[i]) {
// if there are no references to an output tensor, then it is not consumed
// and should not be allocated.
const int output_idx = node->outputs->data[i];
if (output_idx == kTfLiteOptionalTensor) continue;
if (input_tensors_count[output_idx] == 0) {
TfLiteTensor* body_input =
body_subgraph->tensor(body_subgraph->inputs()[i]);
if (body_input->type == kTfLiteString) continue;
if (IsResourceOrVariant(body_input)) continue;
TfLiteTensor* this_output =
this_subgraph->tensor(node->outputs->data[i]);
TfLiteTensorDataFree(this_output);
node->outputs->data[i] = kTfLiteOptionalTensor;
body_input->allocation_type = kTfLiteCustom;
}
}
}
for (int i = 0; i < num_inputs; ++i) {
TfLiteTensor* body_input =
body_subgraph->tensor(body_subgraph->inputs()[i]);
if (!IsResourceOrVariant(body_input)) {
// Set the allocation type to custom to prevent memory allocation.
body_input->allocation_type = kTfLiteCustom;
}
}
TF_LITE_ENSURE_OK(context, body_subgraph->AllocateTensors());
if (body_subgraph->HasDynamicTensors()) {
op_data->body_has_dynamic_output_tensors = true;
} else {
for (int i = 0; i < num_inputs; ++i) {
TfLiteTensor* body_input =
body_subgraph->tensor(body_subgraph->inputs()[i]);
TfLiteTensor* body_output =
body_subgraph->tensor(body_subgraph->outputs()[i]);
TF_LITE_ENSURE_TYPES_EQ(context, body_input->type, body_output->type);
TF_LITE_ENSURE(context, !IsDynamicTensor(body_output));
if (!TfLiteIntArrayEqual(body_input->dims, body_output->dims)) {
// Don't unnecessarily set an output to dynamic when one of input/output
// is a scalar and the other a tensor of size 1.
// If both tensors are scalars or both tensors have shape [1], then
// TfLiteIntArrayEqual would return true. We want to detect when one
// tensor is a scalar and the other has shape [1], so the total number
// of elements is 1.
int total_elements =
(body_input->dims->size > 0 ? body_input->dims->data[0] : 0) +
(body_output->dims->size > 0 ? body_output->dims->data[0] : 0);
if (total_elements == 1) continue;
// If the output shape of the body subgraph is static w.r.t. a fixed
// input size, but it's different from input size, it's still considered
// dynamic. For example: If a subgraph keeps padding its input with a
// fixed padding, the output shape is static w.r.t the input shape and
// padding, but running it in a loop will keep bloating the tensor.
op_data->body_has_dynamic_output_tensors = true;
break;
}
}
}
for (int i = 0; i < num_inputs; ++i) {
if (node->outputs->data[i] == kTfLiteOptionalTensor) continue;
TfLiteTensor* output;
TF_LITE_ENSURE_OK(context, GetOutputSafe(context, node, i, &output));
if (op_data->body_has_dynamic_output_tensors) {
SetTensorToDynamic(output);
} else {
TfLiteTensor* body_output =
body_subgraph->tensor(body_subgraph->outputs()[i]);
TfLiteIntArray* output_size = TfLiteIntArrayCopy(body_output->dims);
TF_LITE_ENSURE_OK(context,
context->ResizeTensor(context, output, output_size));
}
}
op_data->subgraphs_prepared = true;
return kTfLiteOk;
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
Subgraph* this_subgraph = reinterpret_cast<Subgraph*>(context->impl_);
if (this_subgraph->ShouldOptimizeMemoryForLargeTensors()) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
// Call Prepare to ensure input shapes are propagated to the body subgraph.
op_data->subgraphs_prepared = false;
// Apply lazy initialization of WHILE kernel.
// Just make node output tensors dynamic.
int num_outputs = node->outputs->size;
for (int i = 0; i < num_outputs; ++i) {
if (node->outputs->data[i] == kTfLiteOptionalTensor) continue;
TfLiteTensor* output;
TF_LITE_ENSURE_OK(context, GetOutputSafe(context, node, i, &output));
SetTensorToDynamic(output);
}
return kTfLiteOk;
}
return Prepare_impl(context, node);
}
// Evaluate cond subgraph and set the result.
TfLiteStatus Eval_cond_subgraph(TfLiteContext* context, Subgraph* cond_subgraph,
bool cond_has_dynamic_output_tensors,
bool* cond_subgraph_output) {
TF_LITE_ENSURE_OK(context, cond_subgraph->Invoke());
int cond_subgraph_output_index = cond_subgraph->outputs()[0];
cond_subgraph->EnsureTensorDataIsReadable(cond_subgraph_output_index);
TfLiteTensor* cond_output = cond_subgraph->tensor(cond_subgraph_output_index);
if (cond_has_dynamic_output_tensors) {
TF_LITE_ENSURE_STATUS(CheckCondOutput(context, cond_output));
}
*cond_subgraph_output = (cond_output->data.b[0]);
return kTfLiteOk;
}
void SetupUnconsumedOutputs(const TfLiteNode* node, const OpData* op_data,
Subgraph* this_subgraph, Subgraph* body_subgraph) {
const int num_inputs = node->inputs->size;
for (int i = 0; i < num_inputs; ++i) {
if (node->outputs->data[i] == kTfLiteOptionalTensor) {
TfLiteTensor* this_input = this_subgraph->tensor(node->inputs->data[i]);
TfLiteTensor* body_input =
body_subgraph->tensor(body_subgraph->inputs()[i]);
body_input->data.data = this_input->data.data;
}
}
}
// Evaluate WHILE op when body subgraph has dynamic outputs.
TfLiteStatus Eval_dynamic(TfLiteContext* context, TfLiteNode* node) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
Subgraph* this_subgraph = reinterpret_cast<Subgraph*>(context->impl_);
auto* subgraphs = this_subgraph->GetSubgraphs();
Subgraph* cond_subgraph = (*subgraphs)[op_data->cond_subgraph_index].get();
Subgraph* body_subgraph = (*subgraphs)[op_data->body_subgraph_index].get();
// The follow graph illustrates the current implementation.
//
// This Subgraph Cond Subgraph Body Subgraph
// +-----------+ (1) +------------+ +------------+
// | WHILE |-------->| SUBGRAPH | | SUBGRAPH |
// | INPUT | | INPUT | | INPUT |
// | | | ---------------->| |
// | | | / | <---- | |
// +-----------+ +--/---------+ \ +------------+
// | / | \ |
// | (2) (4) / | (3) (6) \ | (5)
// v / v \ v
// +-----------+ / +------------+ +------------+
// | WHILE |--/ | SUBGRAPH | | SUBGRAPH |
// | OUTPUT | (7) | OUTPUT | | OUTPUT |
// | |<-------------------------------| |
// +-----------+ +------------+ +------------+
//
// (1) Copy the inputs of WHILE op to the inputs of condition subgraph.
// (2) Copy the inputs of WHILE op to the outputs of WHILE op
// (3) Invoke condition subgraph.
// Exit the loop if the result is false.
// (4) Copy the outputs of WHILE op to the inputs of body subgraph.
// (5) Invoke body subgraph.
// (6) Copy the outputs of body subgraph to the inputs condition subgraph.
// (7) Copy the outputs of body subgraph to the outputs of WHILE op.
// Jump back to step 3!
//
// If the body subgraph has dynamic sized outputs, it's required to resize the
// tensor before copying in step 1, 2, 4, 6 and 7.
//
// Note the flow is carefully designed to handle the dynamic sized output
// case. The loop invariant is: The newest value is in the inputs of condition
// subgraph. This is always true before step 3.
// Step 1. node->inputs -> cond->inputs (fast)
TF_LITE_ENSURE_OK(context, DeepCopyTensorsShapeTypeData(
context, node, this_subgraph,
TfLiteIntArrayView(node->inputs),
cond_subgraph, cond_subgraph->inputs(),
op_data->body_has_dynamic_output_tensors));
// Step 2. node->inputs -> node->outputs
TF_LITE_ENSURE_OK(
context,
DeepCopyTensorsShapeTypeData(
context, node, this_subgraph, TfLiteIntArrayView(node->inputs),
this_subgraph, TfLiteIntArrayView(node->outputs),
op_data->body_has_dynamic_output_tensors));
SetupUnconsumedOutputs(node, op_data, this_subgraph, body_subgraph);
while (true) {
// Step 3. Eval cond subgraph
bool cond_subgraph_output;
TF_LITE_ENSURE_OK(
context, Eval_cond_subgraph(context, cond_subgraph,
op_data->cond_has_dynamic_output_tensors,
&cond_subgraph_output));
if (!cond_subgraph_output) {
break;
}
// Step 4. node->outputs -> body->inputs
TF_LITE_ENSURE_OK(context, DeepOrShallowCopyTensorsShapeTypeData(
context, node, this_subgraph,
TfLiteIntArrayView(node->outputs),
body_subgraph, body_subgraph->inputs()));
// Step 5. Invoke body subgraph
TF_LITE_ENSURE_OK(context, body_subgraph->Invoke());
for (int tensor_index : body_subgraph->outputs()) {
body_subgraph->EnsureTensorDataIsReadable(tensor_index);
}
// Step 6. body->outputs -> cond->inputs (fast)
TF_LITE_ENSURE_OK(
context, DeepCopyTensorsShapeTypeData(
context, node, body_subgraph, body_subgraph->outputs(),
cond_subgraph, cond_subgraph->inputs(),
op_data->body_has_dynamic_output_tensors));
// Step 7. body->outputs -> node->outputs
TF_LITE_ENSURE_OK(
context, DeepCopyTensorsShapeTypeData(
context, node, body_subgraph, body_subgraph->outputs(),
this_subgraph, TfLiteIntArrayView(node->outputs),
op_data->body_has_dynamic_output_tensors));
}
return kTfLiteOk;
}
// Evaluate WHILE op when body subgraph has static outputs.
TfLiteStatus Eval_static(TfLiteContext* context, TfLiteNode* node) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
Subgraph* this_subgraph = reinterpret_cast<Subgraph*>(context->impl_);
auto* subgraphs = this_subgraph->GetSubgraphs();
Subgraph* cond_subgraph = (*subgraphs)[op_data->cond_subgraph_index].get();
Subgraph* body_subgraph = (*subgraphs)[op_data->body_subgraph_index].get();
// The follow graph illustrates the current implementation.
// The body subgraph input tensors share memory with the node output tensors
// so there is no need to copy from body subgraph output to inputs, only to
// node outputs.
//
// This Subgraph Cond Subgraph Body Subgraph
// +-----------+ (1) +------------+ +------------+
// | WHILE |-------->| SUBGRAPH | | SUBGRAPH |
// | INPUT | | INPUT | | INPUT |
// | | | shared w/ |
// | | | | <---- |WHILE OUTPUT|
// +-----------+ +------------+ \ +------------+
// | | \ |
// | (2) | (3) (5) \ | (4)
// v v \ v
// +-----------+ +------------+ +------------+
// | WHILE | | SUBGRAPH | | SUBGRAPH |
// | OUTPUT | (6) | OUTPUT | | OUTPUT |
// | |<-------------------------------| |
// +-----------+ +------------+ +------------+
//
// (1) Copy the inputs of WHILE op to the inputs of condition subgraph.
// (2) Copy the inputs of WHILE op to the outputs of WHILE op.
// (3) Invoke condition subgraph.
// Break if the result is false.
// (4) Invoke body subgraph.
// (5) Copy the outputs of body subgraph to the inputs condition subgraph.
// (6) Copy the outputs of body subgraph to the outputs of the WHILE op.
//
// The body subgraph shouldn't have dynamic sized outputs.
// Step 1. node->inputs -> cond->inputs (fast)
TF_LITE_ENSURE_OK(
context,
CopyTensorsData(context, this_subgraph, TfLiteIntArrayView(node->inputs),
cond_subgraph, cond_subgraph->inputs()));
// Step 2. node->inputs to node->outputs
TF_LITE_ENSURE_OK(
context,
CopyTensorsData(context, this_subgraph, TfLiteIntArrayView(node->inputs),
this_subgraph, TfLiteIntArrayView(node->outputs)));
const int num_inputs = node->inputs->size;
for (int i = 0; i < num_inputs; ++i) {
if (node->outputs->data[i] == kTfLiteOptionalTensor) continue;
TfLiteTensor* body_input =
body_subgraph->tensor(body_subgraph->inputs()[i]);
TfLiteTensor* this_output = this_subgraph->tensor(node->outputs->data[i]);
body_input->data = this_output->data;
}
SetupUnconsumedOutputs(node, op_data, this_subgraph, body_subgraph);
while (true) {
// Step 3. Eval cond subgraph
bool cond_subgraph_output;
TF_LITE_ENSURE_OK(
context, Eval_cond_subgraph(context, cond_subgraph,
op_data->cond_has_dynamic_output_tensors,
&cond_subgraph_output));
if (!cond_subgraph_output) {
break;
}
// Step 4. Invoke body subgraph
TF_LITE_ENSURE_OK(context, body_subgraph->Invoke());
for (int tensor_index : body_subgraph->outputs()) {
body_subgraph->EnsureTensorDataIsReadable(tensor_index);
}
// Step 5. body->outputs -> cond->inputs (fast)
TF_LITE_ENSURE_OK(
context,
CopyTensorsData(context, body_subgraph, body_subgraph->outputs(),
cond_subgraph, cond_subgraph->inputs()));
TF_LITE_ENSURE_OK(
context,
CopyTensorsData(context, body_subgraph, body_subgraph->outputs(),
this_subgraph, TfLiteIntArrayView(node->outputs)));
}
return kTfLiteOk;
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
Subgraph* this_subgraph = reinterpret_cast<Subgraph*>(context->impl_);
auto* subgraphs = this_subgraph->GetSubgraphs();
Subgraph* cond_subgraph = (*subgraphs)[op_data->cond_subgraph_index].get();
Subgraph* body_subgraph = (*subgraphs)[op_data->body_subgraph_index].get();
if (op_data->subgraphs_prepared == false) {
TF_LITE_ENSURE_OK(context, Prepare_impl(context, node));
} else {
TF_LITE_ENSURE_OK(context, cond_subgraph->AllocateTensors());
TF_LITE_ENSURE_OK(context, body_subgraph->AllocateTensors());
}
if (op_data->body_has_dynamic_output_tensors) {
TF_LITE_ENSURE_OK(context, Eval_dynamic(context, node));
} else {
TF_LITE_ENSURE_OK(context, Eval_static(context, node));
}
if (!this_subgraph->ShouldPreserveAllTensors()) {
TF_LITE_ENSURE_OK(context, cond_subgraph->ReleaseMemory());
TF_LITE_ENSURE_OK(context, body_subgraph->ReleaseMemory());
}
return kTfLiteOk;
}
} // namespace while_kernel
TfLiteRegistration* Register_WHILE() {
static TfLiteRegistration r = {while_kernel::Init, while_kernel::Free,
while_kernel::Prepare, while_kernel::Eval};
return &r;
}
} // namespace builtin
} // namespace ops
} // namespace tflite