Skip to content

Commit 8f1f3b2

Browse files
authored
WHILE operator input/output copy fix (#3633)
@tensorflow/micro Remove extraneous tensor copy operation after first invocation of condition subgraph. Move copy of operator inputs to outputs, such that it occurs before the first invocation of the condition subgraph. This preserves the operator inputs when one or more of them is the output of DECODE, and alternate decompression memory is in use. This is because the output of DECODE is for immediate consumption by the next operator in the graph (WHILE), yet it is possible for the WHILE subgraph invocations to share memory with the original DECODE output. Update the unit test for multiple invocations of the condition and body subgraphs. When copying tensors between operator inputs/outputs and subgraph inputs/outputs, check if the source and destination tensors share memory. bug=fixes #3632
1 parent 0965635 commit 8f1f3b2

3 files changed

Lines changed: 48 additions & 11 deletions

File tree

tensorflow/lite/micro/kernels/kernel_util.cc

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ TfLiteStatus CopyOpInputsToOpOutputs(TfLiteContext* context, TfLiteNode* node) {
166166
TfLiteEvalTensor* output = tflite::micro::GetEvalOutput(context, node, i);
167167
int bytes = ValidateAndGetTensorSizes(input, output);
168168
TF_LITE_ENSURE(context, bytes >= 0);
169-
memcpy(output->data.raw, input->data.raw, bytes);
169+
// Don't copy if the tensors share memory
170+
if (output->data.data != input->data.data) {
171+
memcpy(output->data.data, input->data.data, bytes);
172+
}
170173
}
171174
return kTfLiteOk;
172175
}
@@ -226,7 +229,10 @@ TfLiteStatus CopyOpInputsToSubgraphInputs(TfLiteContext* context,
226229
graph_info->GetSubgraphInput(subgraph_idx, i);
227230
int bytes = ValidateAndGetTensorSizes(input, subgraph_input);
228231
TF_LITE_ENSURE(context, bytes >= 0);
229-
memcpy(subgraph_input->data.raw, input->data.raw, bytes);
232+
// Don't copy if the tensors share memory
233+
if (subgraph_input->data.data != input->data.data) {
234+
memcpy(subgraph_input->data.data, input->data.data, bytes);
235+
}
230236
}
231237
return kTfLiteOk;
232238
}
@@ -243,7 +249,10 @@ TfLiteStatus CopyOpOutputsToSubgraphInputs(TfLiteContext* context,
243249
graph_info->GetSubgraphInput(subgraph_idx, i);
244250
int bytes = ValidateAndGetTensorSizes(output, subgraph_input);
245251
TF_LITE_ENSURE(context, bytes >= 0);
246-
memcpy(subgraph_input->data.raw, output->data.raw, bytes);
252+
// Don't copy if the tensors share memory
253+
if (subgraph_input->data.data != output->data.data) {
254+
memcpy(subgraph_input->data.data, output->data.data, bytes);
255+
}
247256
}
248257
return kTfLiteOk;
249258
}
@@ -261,7 +270,10 @@ TfLiteStatus CopySubgraphOutputsToOpOutputs(TfLiteContext* context,
261270
graph_info->GetSubgraphOutput(subgraph_idx, i);
262271
int bytes = ValidateAndGetTensorSizes(output, subgraph_output);
263272
TF_LITE_ENSURE(context, bytes >= 0);
264-
memcpy(output->data.raw, subgraph_output->data.raw, bytes);
273+
// Don't copy if the tensors share memory
274+
if (output->data.data != subgraph_output->data.data) {
275+
memcpy(output->data.data, subgraph_output->data.data, bytes);
276+
}
265277
}
266278
return kTfLiteOk;
267279
}

tensorflow/lite/micro/kernels/while.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,20 @@ TfLiteStatus WhileEval(TfLiteContext* context, TfLiteNode* node) {
8585
context, node, graph_info, op_data->cond_subgraph_index,
8686
/*first_tensor_idx=*/0));
8787

88+
// Preserve the op inputs by copying to op outputs, prior to invoking
89+
// a subgraph which could invalidate the memory of one or more op inputs.
90+
// This is possible when using the output of the DECODE operator as input to
91+
// the WHILE op, in the presence of alternate decompression memory.
92+
TF_LITE_ENSURE_OK(context,
93+
tflite::micro::CopyOpInputsToOpOutputs(context, node));
94+
8895
TF_LITE_ENSURE_OK(context,
8996
graph_info->InvokeSubgraph(op_data->cond_subgraph_index));
9097

9198
TfLiteEvalTensor* cond_subgraph_output = graph_info->GetSubgraphOutput(
9299
op_data->cond_subgraph_index, /*tensor_idx=*/0);
93100
bool cond_value = cond_subgraph_output->data.b[0];
94101

95-
TF_LITE_ENSURE_OK(context,
96-
tflite::micro::CopyOpInputsToSubgraphInputs(
97-
context, node, graph_info, op_data->body_subgraph_index,
98-
/*first_tensor_idx=*/0));
99-
TF_LITE_ENSURE_OK(context,
100-
tflite::micro::CopyOpInputsToOpOutputs(context, node));
101-
102102
while (cond_value == true) {
103103
// Copy output of this iteration back to the body input.
104104
TF_LITE_ENSURE_OK(

tensorflow/lite/micro/kernels/while_test.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,29 @@ TEST(WhileTest, WhileShouldInvokeOnce) {
7373
EXPECT_EQ(output1->data.f[0], 3.0f);
7474
}
7575

76+
TEST(WhileTest, WhileShouldInvokeMultiple) {
77+
constexpr int kArenaSize = 5000;
78+
uint8_t arena[kArenaSize];
79+
80+
const tflite::Model* model =
81+
tflite::testing::GetSimpleModelWithSubgraphsAndWhile();
82+
tflite::MicroMutableOpResolver<3> resolver;
83+
resolver.AddWhile();
84+
resolver.AddAdd();
85+
resolver.AddLess();
86+
tflite::MicroInterpreter interpreter(model, resolver, arena, kArenaSize);
87+
EXPECT_EQ(kTfLiteOk, interpreter.AllocateTensors());
88+
TfLiteTensor* input0 = interpreter.input(0);
89+
TfLiteTensor* input1 = interpreter.input(1);
90+
TfLiteTensor* output0 = interpreter.output(0);
91+
TfLiteTensor* output1 = interpreter.output(1);
92+
input0->data.f[0] = -5.0f;
93+
input1->data.f[0] = 3.0f;
94+
95+
interpreter.Invoke();
96+
97+
EXPECT_EQ(output0->data.f[0], 4.0f);
98+
EXPECT_EQ(output1->data.f[0], 3.0f);
99+
}
100+
76101
TF_LITE_MICRO_TESTS_MAIN

0 commit comments

Comments
 (0)