Support multiple delegates, especially static ones when no dynamic tensors are present. Also add new tests that focus on multiple delegates.
PiperOrigin-RevId: 373413130
Change-Id: Ifd5bbdaf7c5837558212cf1db5c041df3842274c
diff --git a/tensorflow/lite/c/c_api_experimental_test.cc b/tensorflow/lite/c/c_api_experimental_test.cc
index 2670861..1e170c9 100644
--- a/tensorflow/lite/c/c_api_experimental_test.cc
+++ b/tensorflow/lite/c/c_api_experimental_test.cc
@@ -29,6 +29,7 @@
#include "tensorflow/lite/testing/util.h"
using testing::HasSubstr;
+using tflite::delegates::test_utils::SimpleDelegate;
using tflite::delegates::test_utils::TestDelegate;
namespace {
diff --git a/tensorflow/lite/core/subgraph.cc b/tensorflow/lite/core/subgraph.cc
index 0f58761..f6e3178 100644
--- a/tensorflow/lite/core/subgraph.cc
+++ b/tensorflow/lite/core/subgraph.cc
@@ -1594,44 +1594,8 @@
return kTfLiteDelegateError;
}
- // Restore delegation state if applicable.
- TF_LITE_ENSURE_STATUS(RedoAllDelegates());
-
- if (state_ == kStateInvokableAndImmutable) {
- ReportError(
- "ModifyGraphWithDelegate is disallowed when graph is immutable.");
- return kTfLiteApplicationError;
- }
-
- if (!(delegate->flags & kTfLiteDelegateFlagsAllowDynamicTensors)) {
- int last_execution_plan_index_prepared;
- TF_LITE_ENSURE_OK(
- &context_, PrepareOpsStartingAt(0, execution_plan_,
- &last_execution_plan_index_prepared));
- if (has_dynamic_tensors_) {
- // Make sure that we are in a defined ready state before returning.
- // Plan and allocate tensors before returning.
- TF_LITE_ENSURE_OK(&context_, EnsureMemoryAllocations());
- ReportError(
- "Attempting to use a delegate that only supports static-sized "
- "tensors with a graph that has dynamic-sized tensors.");
- return kTfLiteApplicationError;
- }
- }
-
- const bool was_invokable_before_delegate = state_ == kStateInvokable;
- if (delegates_applied_.empty()) {
- // This is the first delegate being applied, so remember original execution
- // plan.
- // TODO(b/119623453): Restore execution plan to this state if delegate
- // application fails.
- pre_delegation_execution_plan_ = execution_plan_;
- }
-
- // TODO(aselle): Consider if it is worth storing pointers to delegates.
- // Setup additional context interface.
- SwitchToDelegateContext();
-
+ // Resets delegation & leaves graph in consistent state if delegate status is
+ // not okay.
auto reset_delegation_if_not_ok = [this](TfLiteStatus status) {
if (status != kTfLiteOk) {
TF_LITE_ENSURE_STATUS(RemoveAllDelegates());
@@ -1643,14 +1607,59 @@
return kTfLiteOk;
};
- TfLiteStatus status = delegate->Prepare(&context_, delegate);
+ // STEP 1: Verify & prepare graph for delegation.
+ // ==============================================
+ // Restore delegation state if applicable.
+ TF_LITE_ENSURE_STATUS(RedoAllDelegates());
+
+ const bool delegate_supports_dynamic_shapes =
+ delegate->flags & kTfLiteDelegateFlagsAllowDynamicTensors;
+ const auto pre_delegation_state = state_;
+
+ if (state_ == kStateInvokableAndImmutable) {
+ // A delegate that doesn't support dynamic shapes was already applied, so
+ // we can assume tensor shapes have been propagated & there are no dynamic
+ // tensors.
+ // Reset the state to force tensor/op reallocation.
+ state_ = kStateUninvokable;
+ } else if (!delegate_supports_dynamic_shapes) {
+ // Check if graph has dynamic tensors by preparing ops.
+ int last_execution_plan_index_prepared;
+ TF_LITE_ENSURE_STATUS(PrepareOpsStartingAt(
+ 0, execution_plan_, &last_execution_plan_index_prepared));
+ if (has_dynamic_tensors_) {
+ TF_LITE_ENSURE_STATUS(EnsureMemoryAllocations());
+ ReportError(
+ "Attempting to use a delegate that only supports static-sized "
+ "tensors with a graph that has dynamic-sized tensors.");
+ return kTfLiteApplicationError;
+ }
+ }
+
+ if (delegates_applied_.empty()) {
+ // This is the first delegate being applied, so remember original execution
+ // plan.
+ // TODO(b/119623453): Restore execution plan to this state if delegate
+ // application fails.
+ pre_delegation_execution_plan_ = execution_plan_;
+ }
+
+ // STEP 2: Delegate replaces applicable nodes with delegate kernels.
+ // =================================================================
+
+ // Setup additional context interface.
+ SwitchToDelegateContext();
+ TfLiteStatus status = delegate->Prepare(&context_, delegate);
// Remove additional context info.
SwitchToKernelContext();
-
TF_LITE_ENSURE_STATUS(reset_delegation_if_not_ok(status));
- if (!(delegate->flags & kTfLiteDelegateFlagsAllowDynamicTensors)) {
+ // STEP 3: Leave graph in consistent state based on delegate & previous state.
+ // ===========================================================================
+
+ if (!delegate_supports_dynamic_shapes) {
+ // CASE 1: Current delegate does not support dynamic shapes.
// Reset the state to force tensor/op reallocation.
state_ = kStateUninvokable;
TF_LITE_ENSURE_STATUS(
@@ -1658,9 +1667,28 @@
// After using a delegate which doesn't support dynamic tensors, make the
// entire graph immutable.
state_ = kStateInvokableAndImmutable;
- } else if (was_invokable_before_delegate) {
- // If the graph was invokable prior to delegate application, flush
- // allocation now to leave it in a consistent state.
+ } else if (pre_delegation_state == kStateInvokableAndImmutable) {
+ // CASE 2: Current delegate supports dynamic shapes, but a previous one
+ // does not.
+ // Make sure new delegate didn't mark a tensor as dynamic.
+ int last_execution_plan_index_prepared;
+ TF_LITE_ENSURE_STATUS(reset_delegation_if_not_ok(PrepareOpsStartingAt(
+ 0, execution_plan_, &last_execution_plan_index_prepared)));
+ if (has_dynamic_tensors_) {
+ TF_LITE_ENSURE_STATUS(RemoveAllDelegates());
+ ReportError(
+ "Cannot allow dynamic tensors due to previous delegation, resetting "
+ "to original execution plan.");
+ return kTfLiteApplicationError;
+ }
+ // Redo memory allocations & ensure state is set back to original value.
+ TF_LITE_ENSURE_STATUS(
+ reset_delegation_if_not_ok(EnsureMemoryAllocations()));
+ state_ = kStateInvokableAndImmutable;
+ } else if (pre_delegation_state == kStateInvokable) {
+ // CASE 3: Current delegate supports dynamic shapes, and the graph was
+ // previously invokable.
+ // Flush allocation now to leave it in a consistent state.
TF_LITE_ENSURE_STATUS(
reset_delegation_if_not_ok(EnsureMemoryAllocations()));
}
diff --git a/tensorflow/lite/delegates/delegate_test.cc b/tensorflow/lite/delegates/delegate_test.cc
index 66904ea..8992c1e 100644
--- a/tensorflow/lite/delegates/delegate_test.cc
+++ b/tensorflow/lite/delegates/delegate_test.cc
@@ -38,8 +38,10 @@
namespace tflite {
namespace delegates {
+using test_utils::SimpleDelegate;
using test_utils::TestDelegate;
using test_utils::TestFP16Delegation;
+using test_utils::TestTwoDelegates;
namespace {
@@ -130,108 +132,6 @@
}
}
-TEST_F(TestDelegate, SecondDelegationPrepareFailure) {
- // First delegate only supports nodes 1, 2. Gets applied successfully.
- // This delegate should support dynamic tensors, otherwise the second won't be
- // applied.
- delegate_ = std::unique_ptr<SimpleDelegate>(
- new SimpleDelegate({1, 2}, kTfLiteDelegateFlagsAllowDynamicTensors));
- // Second delegate supports node 0, but fails during the delegate-node's
- // Prepare.
- delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
- {0}, kTfLiteDelegateFlagsNone, true /**fail_node_prepare**/));
-
- // Initially, execution plan has 3 nodes.
- ASSERT_EQ(interpreter_->execution_plan().size(), 3);
- // First delegate should be applied successfully, yielding a plan with 2
- // nodes.
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
- kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
- // Second delegate won't get applied.
- // As a result, previous delegate should also get undone, restoring the
- // execution plan to its original state.
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
- kTfLiteDelegateError);
- ASSERT_EQ(interpreter_->execution_plan().size(), 3);
-
- std::vector<float> input = {1.0f, 2.0f, 3.0f};
- std::vector<float> expected_output = {2.0f, 4.0f, 6.0f};
- constexpr int kOutputTensorIndex = 3;
- TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
-
- // Verify Invoke() behavior.
- memcpy(interpreter_->typed_tensor<float>(0), input.data(), 3 * sizeof(float));
- memcpy(interpreter_->typed_tensor<float>(1), input.data(), 3 * sizeof(float));
- interpreter_->Invoke();
- for (int i = 0; i < 3; ++i) {
- EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
- }
-}
-
-TEST_F(TestDelegate, SecondDelegationInvokeFailure) {
- delegate_ = std::unique_ptr<SimpleDelegate>(
- new SimpleDelegate({1, 2}, kTfLiteDelegateFlagsAllowDynamicTensors));
- delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
- {0}, kTfLiteDelegateFlagsNone, false /**fail_node_prepare**/,
- 0 /**min_ops_per_subset**/, true /**fail_node_invoke**/));
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
- kTfLiteOk);
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
- kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- std::vector<float> input = {1.0f, 2.0f, 3.0f};
- // Outputs match the AddOp path, rather than delegate path.
- std::vector<float> expected_output = {2.0f, 4.0f, 6.0f};
- constexpr int kOutputTensorIndex = 3;
-
- // Verify Invoke() behavior to ensure Interpreter isn't broken.
- memcpy(interpreter_->typed_tensor<float>(0), input.data(), 3 * sizeof(float));
- memcpy(interpreter_->typed_tensor<float>(1), input.data(), 3 * sizeof(float));
- EXPECT_EQ(interpreter_->Invoke(), kTfLiteError);
- EXPECT_EQ(RemoveAllDelegates(), kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 3);
- ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
- TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
- for (int i = 0; i < 3; ++i) {
- EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
- }
-}
-
-// This test ensures that node indices in multi-delegate application are handled
-// correctly by the TFLite partitioning algorithm.
-TEST_F(TestDelegate, TwoDelegates_ExecutionPlanIndicesDifferent) {
- // First delegate supports nodes 0, 1.
- // After this delegation, the execution plan size is 2.
- delegate_ = std::unique_ptr<SimpleDelegate>(
- new SimpleDelegate({0, 1}, kTfLiteDelegateFlagsAllowDynamicTensors));
- // Second delegate supports (original) node index 2.
- // The execution plan has 2 nodes, so this verifies that the partitioning
- // algorithm correctly refers to (original) node indices instead of execution
- // plan indices.
- delegate2_ = std::unique_ptr<SimpleDelegate>(
- new SimpleDelegate({2}, kTfLiteDelegateFlagsNone));
-
- ASSERT_EQ(interpreter_->execution_plan().size(), 3);
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
- kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
- kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- // Verify Invoke works.
- ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
-}
-
TEST_F(TestDelegate, StaticDelegateMakesGraphImmutable) {
delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate({0, 1, 2}));
ASSERT_EQ(
@@ -331,39 +231,6 @@
EXPECT_EQ(tensor->buffer_handle, kTfLiteNullBufferHandle);
}
-// We utilize delegation in such a way as to allow node subsets with a minimum
-// number of ops only.
-TEST_F(TestDelegate, TestDelegationWithPartitionPreview) {
- // We set kTfLiteDelegateFlagsAllowDynamicTensors to ensure the second
- // delegate can be applied.
- // Ops 0 and 2 are delegated but end up in the same partition (based on
- // dependency analysis). However, since min_ops_per_subset = 3, no delegation
- // takes place.
- delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
- {0, 2}, kTfLiteDelegateFlagsAllowDynamicTensors,
- false /**fail_node_prepare**/, 3 /**min_ops_per_subset**/));
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate());
-
- // Original execution plan remains.
- ASSERT_EQ(interpreter_->execution_plan().size(), 3);
- ASSERT_EQ(interpreter_->execution_plan()[0], 0);
- ASSERT_EQ(interpreter_->execution_plan()[1], 1);
- ASSERT_EQ(interpreter_->execution_plan()[2], 2);
-
- // Same ops supported, but min_ops_per_subset = 2.
- delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
- {0, 2}, kTfLiteDelegateFlagsAllowDynamicTensors,
- false /**fail_node_prepare**/, 2 /**min_ops_per_subset**/));
- interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate());
-
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
- ASSERT_EQ(interpreter_->execution_plan()[0], 3);
- const auto* node_and_reg = interpreter_->node_and_registration(3);
- ASSERT_EQ(node_and_reg->second.custom_name,
- delegate2_->FakeFusedRegistration().custom_name);
- ASSERT_EQ(interpreter_->execution_plan()[1], 1);
-}
-
TEST_F(TestDelegate, TestResizeInputWithNonDynamicDelegate) {
delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate({0, 1, 2}));
ASSERT_EQ(
@@ -377,9 +244,8 @@
ASSERT_EQ(interpreter_->ResizeInputTensor(0, {1, 3}), kTfLiteOk);
ASSERT_EQ(interpreter_->ResizeInputTensor(1, {1, 3}), kTfLiteOk);
ASSERT_EQ(interpreter_->execution_plan().size(), 3);
- // This should fail, since the previous application of the delegate will be
- // re-done automatically, making the graph immutable again.
- ASSERT_NE(
+ // ModifyGraphWithDelegate shouldn't fail, but graph won't change.
+ ASSERT_EQ(
interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
kTfLiteOk);
// Ensure graph has been restored to its valid delegated state.
@@ -413,68 +279,6 @@
}
}
-TEST_F(TestDelegate, TestResizeInputWithMultipleDelegates) {
- // First delegate only supports node 0.
- // This delegate should support dynamic tensors, otherwise the second won't be
- // applied.
- delegate_ = std::unique_ptr<SimpleDelegate>(
- new SimpleDelegate({0}, kTfLiteDelegateFlagsAllowDynamicTensors));
- // Second delegate supports nodes 1 & 2, and makes the graph immutable.
- delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate({1, 2}));
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
- kTfLiteOk);
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
- kTfLiteOk);
- // Should be two delegates nodes.
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- // Try resizing input to same shape as before (which should be a No-op).
- ASSERT_EQ(interpreter_->ResizeInputTensor(0, {3}), kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- // Resizing input tensors should temporarily restore original execution plan
- // of 3 nodes.
- ASSERT_EQ(interpreter_->ResizeInputTensor(0, {1, 3}), kTfLiteOk);
- ASSERT_EQ(interpreter_->ResizeInputTensor(1, {1, 3}), kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 3);
- // This should fail, since the previous application of the delegate will be
- // re-done automatically, making the graph immutable again.
- ASSERT_NE(
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
- kTfLiteOk);
- // Ensure graph has been restored to its valid delegated state.
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- std::vector<float> input = {1.0f, 2.0f, 3.0f, 4.0f};
- std::vector<float> expected_output = {2.0f, 4.0f, 6.0f, 8.0f};
- constexpr int kOutputTensorIndex = 2;
- TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
-
- // Verify Invoke() behavior.
- memcpy(interpreter_->typed_tensor<float>(0), input.data(), 3 * sizeof(float));
- memcpy(interpreter_->typed_tensor<float>(1), input.data(), 3 * sizeof(float));
- interpreter_->Invoke();
- for (int i = 0; i < 3; ++i) {
- EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
- }
-
- // Resize again, but call AllocateTensors as usual afterwards.
- ASSERT_EQ(interpreter_->ResizeInputTensor(0, {1, 4}), kTfLiteOk);
- ASSERT_EQ(interpreter_->ResizeInputTensor(1, {1, 4}), kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 3);
- ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- memcpy(interpreter_->typed_tensor<float>(0), input.data(), 4 * sizeof(float));
- memcpy(interpreter_->typed_tensor<float>(1), input.data(), 4 * sizeof(float));
- interpreter_->Invoke();
- for (int i = 0; i < 4; ++i) {
- EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
- }
-}
-
// If a delegate sets kTfLiteDelegateFlagsRequirePropagatedShapes but not
// kTfLiteDelegateFlagsAllowDynamicTensors, the former is redundant.
TEST_F(TestDelegate, TestRequirePropagatedShapes_NonDynamicDelegate) {
@@ -509,10 +313,8 @@
// propagated by runtime.
int delegate_flags = kTfLiteDelegateFlagsAllowDynamicTensors |
kTfLiteDelegateFlagsRequirePropagatedShapes;
- delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
- {0, 1, 2}, delegate_flags, false /**fail_node_prepare**/,
- 3 /**min_ops_per_subset**/, false /**fail_node_invoke**/,
- true /**automatic_shape_propagation**/));
+ delegate_ = SimpleDelegate::DelegateWithRuntimeShapePropagation(
+ {0, 1, 2}, delegate_flags, 3);
ASSERT_EQ(
interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
kTfLiteOk);
@@ -542,10 +344,8 @@
// Delegate sets both flags and in its Prepare, ensures that shapes have been
// propagated by runtime.
int delegate_flags = kTfLiteDelegateFlagsAllowDynamicTensors;
- delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
- {0, 1, 2}, delegate_flags, false /**fail_node_prepare**/,
- 3 /**min_ops_per_subset**/, false /**fail_node_invoke**/,
- true /**automatic_shape_propagation**/));
+ delegate_ = SimpleDelegate::DelegateWithRuntimeShapePropagation(
+ {0, 1, 2}, delegate_flags, 3);
ASSERT_EQ(
interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
kTfLiteOk);
@@ -556,93 +356,6 @@
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteError);
}
-TEST_F(TestDelegate, TestRequirePropagatedShapes_MultipleDelegates) {
- // First delegate needs to support dynamic tensors to allow second delegation.
- // This delegate does not require automatic propagation.
- delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
- {0}, kTfLiteDelegateFlagsAllowDynamicTensors,
- false /**fail_node_prepare**/, 1 /**min_ops_per_subset**/,
- false /**fail_node_invoke**/, false /**automatic_shape_propagation**/));
- // Second delegate supports nodes 1 & 2, and requires automatic shape
- // propagation.
- int delegate_flags = kTfLiteDelegateFlagsAllowDynamicTensors |
- kTfLiteDelegateFlagsRequirePropagatedShapes;
- delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
- {1, 2}, delegate_flags, false /**fail_node_prepare**/,
- 1 /**min_ops_per_subset**/, false /**fail_node_invoke**/,
- true /**automatic_shape_propagation**/));
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
- kTfLiteOk);
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
- kTfLiteOk);
- // Should be two delegate nodes.
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- ASSERT_EQ(interpreter_->ResizeInputTensor(0, {1, 4}), kTfLiteOk);
- ASSERT_EQ(interpreter_->ResizeInputTensor(1, {1, 4}), kTfLiteOk);
- ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- std::vector<float> input = {1.0f, 2.0f, 3.0f, 4.0f};
- std::vector<float> expected_output = {2.0f, 4.0f, 6.0f, 8.0f};
- constexpr int kOutputTensorIndex = 2;
- TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
-
- memcpy(interpreter_->typed_tensor<float>(0), input.data(), 4 * sizeof(float));
- memcpy(interpreter_->typed_tensor<float>(1), input.data(), 4 * sizeof(float));
- interpreter_->Invoke();
- for (int i = 0; i < 4; ++i) {
- EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
- }
-}
-
-TEST_F(TestDelegate, ReleaseNonPersistentMemoryWithDelegates) {
- // First delegate only supports node 0.
- // This delegate should support dynamic tensors, otherwise the second won't be
- // applied.
- delegate_ = std::unique_ptr<SimpleDelegate>(
- new SimpleDelegate({0}, kTfLiteDelegateFlagsAllowDynamicTensors));
- // Second delegate supports nodes 1 & 2, and makes the graph immutable.
- delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate({1, 2}));
-
- // No-op.
- ASSERT_EQ(interpreter_->ReleaseNonPersistentMemory(), kTfLiteOk);
-
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
- kTfLiteOk);
- ASSERT_EQ(
- interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
- kTfLiteOk);
- // Should be two delegates nodes.
- ASSERT_EQ(interpreter_->execution_plan().size(), 2);
-
- ASSERT_EQ(interpreter_->ReleaseNonPersistentMemory(), kTfLiteOk);
- ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
-
- // This should fail, since the graph is immutable.
- ASSERT_NE(
- interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
- kTfLiteOk);
-
- std::vector<float> input = {1.0f, 2.0f, 3.0f, 4.0f};
- std::vector<float> expected_output = {2.0f, 4.0f, 6.0f, 8.0f};
- constexpr int kOutputTensorIndex = 2;
- TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
-
- // Verify Invoke() behavior.
- memcpy(interpreter_->typed_tensor<float>(0), input.data(), 3 * sizeof(float));
- memcpy(interpreter_->typed_tensor<float>(1), input.data(), 3 * sizeof(float));
- interpreter_->Invoke();
- for (int i = 0; i < 3; ++i) {
- EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
- }
-
- ASSERT_EQ(interpreter_->ReleaseNonPersistentMemory(), kTfLiteOk);
-}
-
TEST_F(TestDelegate, TestCopyFromBufferInvoke) {
delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate({0, 1, 2}));
TfLiteDelegate* delegate = delegate_->get_tf_lite_delegate();
@@ -804,6 +517,338 @@
delegate_->FakeFusedRegistration().custom_name);
}
+TEST_P(TestTwoDelegates, SecondDelegationPrepareFailure) {
+ auto delegate_flag_pair = GetParam();
+ // First delegate only supports nodes 1, 2. Gets applied successfully.
+ delegate_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({1, 2}, delegate_flag_pair.first));
+ // Second delegate supports node 0, but fails during the delegate-node's
+ // Prepare.
+ delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
+ {0}, delegate_flag_pair.second, true /**fail_node_prepare**/));
+
+ // Initially, execution plan has 3 nodes.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 3);
+ if (delegate_flag_pair.first == kTfLiteDelegateFlagsAllowDynamicTensors &&
+ delegate_flag_pair.second == kTfLiteDelegateFlagsAllowDynamicTensors) {
+ // If both delegates support dynamic tensors, the execution plan isn't
+ // prepared by ModifyGraphWithDelegate unless the graph was previously
+ // invokable. This is mainly because dynamic tensors anyway need
+ // allocations during Invoke.
+ // But for this test, we call AllocateTensors() to trigger allocations.
+ ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
+ }
+ // First delegate should be applied successfully, yielding a plan with 2
+ // nodes.
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+
+ // Second delegate won't get applied.
+ // As a result, previous delegate should also get undone, restoring the
+ // execution plan to its original state.
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
+ kTfLiteDelegateError);
+ ASSERT_EQ(interpreter_->execution_plan().size(), 3);
+
+ std::vector<float> input = {1.0f, 2.0f, 3.0f};
+ std::vector<float> expected_output = {2.0f, 4.0f, 6.0f};
+ constexpr int kOutputTensorIndex = 3;
+ TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
+
+ // Verify Invoke() behavior.
+ memcpy(interpreter_->typed_tensor<float>(0), input.data(), 3 * sizeof(float));
+ memcpy(interpreter_->typed_tensor<float>(1), input.data(), 3 * sizeof(float));
+ interpreter_->Invoke();
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
+ }
+}
+
+TEST_P(TestTwoDelegates, SecondDelegationInvokeFailure) {
+ auto delegate_flag_pair = GetParam();
+ delegate_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({1, 2}, delegate_flag_pair.first));
+ delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
+ {0}, delegate_flag_pair.second, false /**fail_node_prepare**/,
+ 0 /**min_ops_per_subset**/, true /**fail_node_invoke**/));
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+ if (delegate_flag_pair.first == kTfLiteDelegateFlagsAllowDynamicTensors &&
+ delegate_flag_pair.second == kTfLiteDelegateFlagsAllowDynamicTensors) {
+ // If both delegates support dynamic tensors, the execution plan isn't
+ // prepared by ModifyGraphWithDelegate unless the graph was previously
+ // invokable. This is mainly because dynamic tensors anyway need
+ // allocations during Invoke.
+ // Call AllocateTensors() to trigger allocations.
+ ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
+ }
+
+ std::vector<float> input = {1.0f, 2.0f, 3.0f};
+ // Outputs match the AddOp path, rather than delegate path.
+ std::vector<float> expected_output = {2.0f, 4.0f, 6.0f};
+ constexpr int kOutputTensorIndex = 3;
+
+ // Verify Invoke() behavior to ensure Interpreter isn't broken.
+ memcpy(interpreter_->typed_tensor<float>(0), input.data(), 3 * sizeof(float));
+ memcpy(interpreter_->typed_tensor<float>(1), input.data(), 3 * sizeof(float));
+ EXPECT_EQ(interpreter_->Invoke(), kTfLiteError);
+ EXPECT_EQ(RemoveAllDelegates(), kTfLiteOk);
+ ASSERT_EQ(interpreter_->execution_plan().size(), 3);
+ ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
+ TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
+ }
+}
+
+// This test ensures that node indices in multi-delegate application are handled
+// correctly by the TFLite partitioning algorithm.
+TEST_P(TestTwoDelegates, NodeIndicesCorrectlyHandledAfterDelegation) {
+ auto delegate_flag_pair = GetParam();
+ // First delegate supports nodes 0, 1.
+ delegate_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({0, 1}, delegate_flag_pair.first));
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+
+ // Second delegate supports (original) node index 2.
+ // The execution plan has 2 nodes, so this verifies that the partitioning
+ // algorithm correctly refers to (original) node indices instead of execution
+ // plan indices.
+ delegate2_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({2}, delegate_flag_pair.second));
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+}
+
+TEST_P(TestTwoDelegates, TestResizeInputTensors) {
+ auto delegate_flag_pair = GetParam();
+ // First delegate only supports node 0.
+ delegate_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({0}, delegate_flag_pair.first));
+ // Second delegate supports nodes 1 & 2.
+ delegate2_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({1, 2}, delegate_flag_pair.second));
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ // Should be two delegated nodes.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+
+ // Try resizing input to same shape as before (which should be a No-op).
+ ASSERT_EQ(interpreter_->ResizeInputTensor(0, {3}), kTfLiteOk);
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+
+ // Resize inputs to new shape.
+ ASSERT_EQ(interpreter_->ResizeInputTensor(0, {1, 4}), kTfLiteOk);
+ ASSERT_EQ(interpreter_->ResizeInputTensor(1, {1, 4}), kTfLiteOk);
+ if (delegate_flag_pair.first == kTfLiteDelegateFlagsAllowDynamicTensors &&
+ delegate_flag_pair.second == kTfLiteDelegateFlagsAllowDynamicTensors) {
+ // If both delegates support dynamic tensors, execution plan won't be reset.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+ } else {
+ // In the presence of a static delegate, the runtime will reset execution
+ // plan to its original state until AllocateTensors or
+ // ModifyGraphWithDelegate
+ ASSERT_EQ(interpreter_->execution_plan().size(), 3);
+ }
+
+ ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
+ // Irrespective of whether one or more delegates support dynamic shapes,
+ // execution plan should have 2 (delegated) nodes now.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+
+ std::vector<float> input = {1.0f, 2.0f, 3.0f, 4.0f};
+ std::vector<float> expected_output = {2.0f, 4.0f, 6.0f, 8.0f};
+ constexpr int kOutputTensorIndex = 2;
+ TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
+
+ // Verify Invoke() behavior.
+ memcpy(interpreter_->typed_tensor<float>(0), input.data(), 4 * sizeof(float));
+ memcpy(interpreter_->typed_tensor<float>(1), input.data(), 4 * sizeof(float));
+ interpreter_->Invoke();
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
+ }
+}
+
+// We utilize delegation in such a way as to allow node subsets with a minimum
+// number of ops only.
+TEST_P(TestTwoDelegates, TestDelegationWithPartitionPreview) {
+ auto delegate_flag_pair = GetParam();
+ // Ops 0 and 2 are delegated but end up in the same partition (based on
+ // dependency analysis). However, since min_ops_per_subset = 3, no delegation
+ // takes place.
+ delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
+ {0, 2}, delegate_flag_pair.first, false /**fail_node_prepare**/,
+ 3 /**min_ops_per_subset**/));
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate());
+
+ // Original execution plan remains.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 3);
+ ASSERT_EQ(interpreter_->execution_plan()[0], 0);
+ ASSERT_EQ(interpreter_->execution_plan()[1], 1);
+ ASSERT_EQ(interpreter_->execution_plan()[2], 2);
+
+ // Same ops supported, but min_ops_per_subset = 2.
+ delegate2_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
+ {0, 2}, delegate_flag_pair.second, false /**fail_node_prepare**/,
+ 2 /**min_ops_per_subset**/));
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate());
+
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+ ASSERT_EQ(interpreter_->execution_plan()[0], 3);
+ const auto* node_and_reg = interpreter_->node_and_registration(3);
+ ASSERT_EQ(node_and_reg->second.custom_name,
+ delegate2_->FakeFusedRegistration().custom_name);
+ ASSERT_EQ(interpreter_->execution_plan()[1], 1);
+}
+
+TEST_P(TestTwoDelegates, TestRequirePropagatedShapes) {
+ // We do not use kTfLiteDelegateFlagsNone in this test, since shape
+ // propagation always requires the delegate to support dynamic tensors. This
+ // delegate does not require automatic propagation.
+ delegate_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({0}, kTfLiteDelegateFlagsAllowDynamicTensors));
+ // Second delegate supports nodes 1 & 2, and requires automatic shape
+ // propagation.
+ int delegate_flags = kTfLiteDelegateFlagsAllowDynamicTensors |
+ kTfLiteDelegateFlagsRequirePropagatedShapes;
+ delegate2_ = SimpleDelegate::DelegateWithRuntimeShapePropagation(
+ {1, 2}, delegate_flags, 1);
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ // Should be two delegate nodes.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+
+ ASSERT_EQ(interpreter_->ResizeInputTensor(0, {1, 4}), kTfLiteOk);
+ ASSERT_EQ(interpreter_->ResizeInputTensor(1, {1, 4}), kTfLiteOk);
+ ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+
+ std::vector<float> input = {1.0f, 2.0f, 3.0f, 4.0f};
+ std::vector<float> expected_output = {2.0f, 4.0f, 6.0f, 8.0f};
+ constexpr int kOutputTensorIndex = 2;
+ TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
+
+ memcpy(interpreter_->typed_tensor<float>(0), input.data(), 4 * sizeof(float));
+ memcpy(interpreter_->typed_tensor<float>(1), input.data(), 4 * sizeof(float));
+ interpreter_->Invoke();
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
+ }
+}
+
+TEST_P(TestTwoDelegates, ReleaseNonPersistentMemoryWithDelegates) {
+ auto delegate_flag_pair = GetParam();
+ // First delegate only supports node 0.
+ delegate_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({0}, delegate_flag_pair.first));
+ // Second delegate supports nodes 1 & 2, and makes the graph immutable.
+ delegate2_ = std::unique_ptr<SimpleDelegate>(
+ new SimpleDelegate({1, 2}, delegate_flag_pair.second));
+
+ // No-op.
+ ASSERT_EQ(interpreter_->ReleaseNonPersistentMemory(), kTfLiteOk);
+
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ // Should be two delegates nodes.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 2);
+
+ ASSERT_EQ(interpreter_->ReleaseNonPersistentMemory(), kTfLiteOk);
+ ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
+
+ std::vector<float> input = {1.0f, 2.0f, 3.0f, 4.0f};
+ std::vector<float> expected_output = {2.0f, 4.0f, 6.0f, 8.0f};
+ constexpr int kOutputTensorIndex = 2;
+ TfLiteTensor* tensor = interpreter_->tensor(kOutputTensorIndex);
+
+ // Verify Invoke() behavior.
+ memcpy(interpreter_->typed_tensor<float>(0), input.data(), 3 * sizeof(float));
+ memcpy(interpreter_->typed_tensor<float>(1), input.data(), 3 * sizeof(float));
+ interpreter_->Invoke();
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(tensor->data.f[i], expected_output[i]) << i;
+ }
+
+ ASSERT_EQ(interpreter_->ReleaseNonPersistentMemory(), kTfLiteOk);
+}
+
+// This test ensures that after a static delegate is applied, a future delegate
+// that accepts previous nodes doesn't make them dynamic.
+TEST_F(TestTwoDelegates, DynamicTensorBeforeStaticDelegate) {
+ // First delegate only supports node {1, 2}.
+ // This makes the graph immutable.
+ delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate({1, 2}));
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ // Second delegate supports node 0, & tries to mark its output as
+ // dynamic. This should result in kTfLiteApplicationError.
+ delegate2_ = SimpleDelegate::DelegateWithDynamicOutput({0});
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
+ kTfLiteApplicationError);
+ // Execution plan reset to original.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 3);
+ ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
+}
+
+// Same as bove, except a tensor later in the graph is marked static.
+// Even in this case, to be safe, we return an error.
+TEST_F(TestTwoDelegates, DynamicTensorAfterStaticDelegate) {
+ // First delegate only supports node 0.
+ // This makes the graph immutable.
+ delegate_ = std::unique_ptr<SimpleDelegate>(new SimpleDelegate({0}));
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate_->get_tf_lite_delegate()),
+ kTfLiteOk);
+ // Second delegate supports node 1, 2 & tries to mark its output as
+ // dynamic. This should result in kTfLiteApplicationError.
+ delegate2_ = SimpleDelegate::DelegateWithDynamicOutput({1, 2});
+ ASSERT_EQ(
+ interpreter_->ModifyGraphWithDelegate(delegate2_->get_tf_lite_delegate()),
+ kTfLiteApplicationError);
+ // Execution plan reset to original.
+ ASSERT_EQ(interpreter_->execution_plan().size(), 3);
+ ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TestTwoDelegatesInstance, TestTwoDelegates,
+ ::testing::Values(std::make_pair(kTfLiteDelegateFlagsNone,
+ kTfLiteDelegateFlagsNone),
+ std::make_pair(kTfLiteDelegateFlagsAllowDynamicTensors,
+ kTfLiteDelegateFlagsNone),
+ std::make_pair(kTfLiteDelegateFlagsNone,
+ kTfLiteDelegateFlagsAllowDynamicTensors),
+ std::make_pair(kTfLiteDelegateFlagsAllowDynamicTensors,
+ kTfLiteDelegateFlagsAllowDynamicTensors)));
+
class TestDelegateWithDynamicTensors : public ::testing::Test {
protected:
void SetUp() override {
diff --git a/tensorflow/lite/delegates/delegate_test_util.cc b/tensorflow/lite/delegates/delegate_test_util.cc
index 878b106..0d62b30 100644
--- a/tensorflow/lite/delegates/delegate_test_util.cc
+++ b/tensorflow/lite/delegates/delegate_test_util.cc
@@ -89,12 +89,7 @@
return reg;
}
-void TestDelegate::SetUp() {
- interpreter_.reset(new Interpreter);
- SetUpSubgraph(&interpreter_->primary_subgraph());
-}
-
-void TestDelegate::SetUpSubgraph(Subgraph* subgraph) {
+void TestDelegation::SetUpSubgraph(Subgraph* subgraph) {
subgraph->AddTensors(5);
subgraph->SetInputs({0, 1});
subgraph->SetOutputs({3, 4});
@@ -120,23 +115,44 @@
&node_index_ignored);
}
+void TestDelegate::SetUp() {
+ interpreter_.reset(new Interpreter);
+ SetUpSubgraph(&interpreter_->primary_subgraph());
+}
+
void TestDelegate::TearDown() {
// Interpreter relies on delegate to free the resources properly. Thus
// the life cycle of delegate must be longer than interpreter.
interpreter_.reset();
delegate_.reset();
+ delegate2_.reset();
}
-TestDelegate::SimpleDelegate::SimpleDelegate(
- const std::vector<int>& nodes, int64_t delegate_flags,
- bool fail_node_prepare, int min_ops_per_subset, bool fail_node_invoke,
- bool automatic_shape_propagation, bool custom_op)
+void TestTwoDelegates::SetUp() {
+ interpreter_.reset(new Interpreter);
+ SetUpSubgraph(&interpreter_->primary_subgraph());
+}
+
+void TestTwoDelegates::TearDown() {
+ // Interpreter relies on delegate to free the resources properly. Thus
+ // the life cycle of delegate must be longer than interpreter.
+ interpreter_.reset();
+ delegate_.reset();
+ delegate2_.reset();
+}
+
+SimpleDelegate::SimpleDelegate(const std::vector<int>& nodes,
+ int64_t delegate_flags, bool fail_node_prepare,
+ int min_ops_per_subset, bool fail_node_invoke,
+ bool automatic_shape_propagation, bool custom_op,
+ bool set_output_tensor_dynamic)
: nodes_(nodes),
fail_delegate_node_prepare_(fail_node_prepare),
min_ops_per_subset_(min_ops_per_subset),
fail_delegate_node_invoke_(fail_node_invoke),
automatic_shape_propagation_(automatic_shape_propagation),
- custom_op_(custom_op) {
+ custom_op_(custom_op),
+ set_output_tensor_dynamic_(set_output_tensor_dynamic) {
delegate_.Prepare = [](TfLiteContext* context,
TfLiteDelegate* delegate) -> TfLiteStatus {
auto* simple = static_cast<SimpleDelegate*>(delegate->data_);
@@ -244,7 +260,7 @@
delegate_.flags = delegate_flags;
}
-TfLiteRegistration TestDelegate::SimpleDelegate::FakeFusedRegistration() {
+TfLiteRegistration SimpleDelegate::FakeFusedRegistration() {
TfLiteRegistration reg = {nullptr};
reg.custom_name = "fake_fused_op";
@@ -304,6 +320,13 @@
reg.prepare = [](TfLiteContext* context, TfLiteNode* node) {
return kTfLiteError;
};
+ } else if (set_output_tensor_dynamic_) {
+ reg.prepare = [](TfLiteContext* context, TfLiteNode* node) {
+ TfLiteTensor* output;
+ TF_LITE_ENSURE_OK(context, GetOutputSafe(context, node, 0, &output));
+ SetTensorToDynamic(output);
+ return kTfLiteOk;
+ };
} else {
reg.prepare = [](TfLiteContext* context, TfLiteNode* node) {
// Set output size to input size
@@ -328,6 +351,26 @@
return reg;
}
+std::unique_ptr<SimpleDelegate>
+SimpleDelegate::DelegateWithRuntimeShapePropagation(
+ const std::vector<int>& nodes, int64_t delegate_flags,
+ int min_ops_per_subset) {
+ return std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
+ nodes, delegate_flags, false /**fail_node_prepare**/,
+ min_ops_per_subset /**min_ops_per_subset**/, false /**fail_node_invoke**/,
+ true /**automatic_shape_propagation**/));
+}
+
+std::unique_ptr<SimpleDelegate> SimpleDelegate::DelegateWithDynamicOutput(
+ const std::vector<int>& nodes) {
+ // All params default except nodes & set_output_tensor_dynamic.
+ return std::unique_ptr<SimpleDelegate>(new SimpleDelegate(
+ nodes, kTfLiteDelegateFlagsAllowDynamicTensors,
+ false /**fail_node_prepare**/, 0 /**min_ops_per_subset**/,
+ false /**fail_node_invoke**/, false /**automatic_shape_propagation**/,
+ true /**custom_op**/, true /**set_output_tensor_dynamic**/));
+}
+
void TestFP16Delegation::SetUp() {
interpreter_.reset(new Interpreter);
interpreter_->AddTensors(13);
diff --git a/tensorflow/lite/delegates/delegate_test_util.h b/tensorflow/lite/delegates/delegate_test_util.h
index 914fd85..bea97e3 100644
--- a/tensorflow/lite/delegates/delegate_test_util.h
+++ b/tensorflow/lite/delegates/delegate_test_util.h
@@ -18,6 +18,7 @@
#include <stdint.h>
#include <memory>
+#include <utility>
#include <vector>
#include <gtest/gtest.h>
@@ -33,8 +34,73 @@
// tensor inputs to produce a tensor output.
TfLiteRegistration AddOpRegistration();
-// TestDelegate is a friend of Interpreter to access RemoveAllDelegates().
-class TestDelegate : public ::testing::Test {
+class SimpleDelegate {
+ public:
+ // Create a simple implementation of a TfLiteDelegate. We use the C++ class
+ // SimpleDelegate and it can produce a handle TfLiteDelegate that is
+ // value-copyable and compatible with TfLite.
+ //
+ // Parameters:
+ // nodes: Indices of the graph nodes that the delegate will handle.
+ // fail_node_prepare: To simulate failure of Delegate node's Prepare().
+ // min_ops_per_subset: If >0, partitioning preview is used to choose only
+ // those subsets with min_ops_per_subset number of nodes.
+ // fail_node_invoke: To simulate failure of Delegate node's Invoke().
+ // automatic_shape_propagation: This assumes that the runtime will
+ // propagate shapes using the original execution plan.
+ // custom_op: If true, the graph nodes specified in the 'nodes' parameter
+ // should be custom ops with name "my_add"; if false, they should be
+ // the builtin ADD operator.
+ // set_output_tensor_dynamic: If True, this delegate sets output tensor to
+ // as dynamic during kernel Prepare.
+ explicit SimpleDelegate(const std::vector<int>& nodes,
+ int64_t delegate_flags = kTfLiteDelegateFlagsNone,
+ bool fail_node_prepare = false,
+ int min_ops_per_subset = 0,
+ bool fail_node_invoke = false,
+ bool automatic_shape_propagation = false,
+ bool custom_op = true,
+ bool set_output_tensor_dynamic = false);
+
+ static std::unique_ptr<SimpleDelegate> DelegateWithRuntimeShapePropagation(
+ const std::vector<int>& nodes, int64_t delegate_flags,
+ int min_ops_per_subset);
+
+ static std::unique_ptr<SimpleDelegate> DelegateWithDynamicOutput(
+ const std::vector<int>& nodes);
+
+ TfLiteRegistration FakeFusedRegistration();
+
+ TfLiteDelegate* get_tf_lite_delegate() { return &delegate_; }
+
+ int min_ops_per_subset() { return min_ops_per_subset_; }
+
+ private:
+ std::vector<int> nodes_;
+ TfLiteDelegate delegate_;
+ bool fail_delegate_node_prepare_ = false;
+ int min_ops_per_subset_ = 0;
+ bool fail_delegate_node_invoke_ = false;
+ bool automatic_shape_propagation_ = false;
+ bool custom_op_ = true;
+ bool set_output_tensor_dynamic_ = false;
+};
+
+// Base class for single/multiple delegate tests.
+// Friend of Interpreter to access RemoveAllDelegates().
+class TestDelegation {
+ protected:
+ TfLiteStatus RemoveAllDelegates() {
+ return interpreter_->RemoveAllDelegates();
+ }
+
+ void SetUpSubgraph(Subgraph* subgraph);
+
+ std::unique_ptr<Interpreter> interpreter_;
+};
+
+// Tests scenarios involving a single delegate.
+class TestDelegate : public TestDelegation, public ::testing::Test {
protected:
void SetUp() override;
@@ -44,55 +110,20 @@
TfLiteBufferHandle AllocateBufferHandle() { return ++last_allocated_handle_; }
- TfLiteStatus RemoveAllDelegates() {
- return interpreter_->RemoveAllDelegates();
- }
+ std::unique_ptr<SimpleDelegate> delegate_, delegate2_;
+};
- void SetUpSubgraph(Subgraph* subgraph);
-
+// Tests scenarios involving two delegates, parametrized by the first & second
+// delegate's flags.
+class TestTwoDelegates
+ : public TestDelegation,
+ public ::testing::TestWithParam<
+ std::pair<TfLiteDelegateFlags, TfLiteDelegateFlags>> {
protected:
- class SimpleDelegate {
- public:
- // Create a simple implementation of a TfLiteDelegate. We use the C++ class
- // SimpleDelegate and it can produce a handle TfLiteDelegate that is
- // value-copyable and compatible with TfLite.
- //
- // Parameters:
- // nodes: Indices of the graph nodes that the delegate will handle.
- // fail_node_prepare: To simulate failure of Delegate node's Prepare().
- // min_ops_per_subset: If >0, partitioning preview is used to choose only
- // those subsets with min_ops_per_subset number of nodes.
- // fail_node_invoke: To simulate failure of Delegate node's Invoke().
- // automatic_shape_propagation: This assumes that the runtime will
- // propagate shapes using the original execution plan.
- // custom_op: If true, the graph nodes specified in the 'nodes' parameter
- // should be custom ops with name "my_add"; if false, they should be
- // the builtin ADD operator.
- explicit SimpleDelegate(const std::vector<int>& nodes,
- int64_t delegate_flags = kTfLiteDelegateFlagsNone,
- bool fail_node_prepare = false,
- int min_ops_per_subset = 0,
- bool fail_node_invoke = false,
- bool automatic_shape_propagation = false,
- bool custom_op = true);
+ void SetUp() override;
- TfLiteRegistration FakeFusedRegistration();
+ void TearDown() override;
- TfLiteDelegate* get_tf_lite_delegate() { return &delegate_; }
-
- int min_ops_per_subset() { return min_ops_per_subset_; }
-
- private:
- std::vector<int> nodes_;
- TfLiteDelegate delegate_;
- bool fail_delegate_node_prepare_ = false;
- int min_ops_per_subset_ = 0;
- bool fail_delegate_node_invoke_ = false;
- bool automatic_shape_propagation_ = false;
- bool custom_op_ = true;
- };
-
- std::unique_ptr<Interpreter> interpreter_;
std::unique_ptr<SimpleDelegate> delegate_, delegate2_;
};
diff --git a/tensorflow/lite/delegates/interpreter_utils_test.cc b/tensorflow/lite/delegates/interpreter_utils_test.cc
index d29b1f8..f30543a 100644
--- a/tensorflow/lite/delegates/interpreter_utils_test.cc
+++ b/tensorflow/lite/delegates/interpreter_utils_test.cc
@@ -29,6 +29,7 @@
namespace tflite {
namespace delegates {
+using test_utils::SimpleDelegate;
using test_utils::TestDelegate;
using test_utils::TestFP16Delegation;
diff --git a/tensorflow/lite/interpreter.h b/tensorflow/lite/interpreter.h
index 20ed8d2..c6232cd 100644
--- a/tensorflow/lite/interpreter.h
+++ b/tensorflow/lite/interpreter.h
@@ -52,7 +52,7 @@
class InterpreterUtils; // Class for friend declarations.
namespace test_utils {
-class TestDelegate; // Class for friend declarations.
+class TestDelegation; // Class for friend declarations.
} // namespace test_utils
} // namespace delegates
@@ -683,7 +683,7 @@
friend class InterpreterBuilder;
friend class tflite::InterpreterTest;
friend class tflite::delegates::InterpreterUtils;
- friend class tflite::delegates::test_utils::TestDelegate;
+ friend class tflite::delegates::test_utils::TestDelegation;
/// Set the value of an external context.
static void SetExternalContext(struct TfLiteContext* context,