Skip to content

Commit 5d44897

Browse files
committed
[3/N] Add get_option/set_option function in backend interface
Pull Request resolved: #11391 Add update function in backend interface class. The update function will receive the backend options from dispatched by the ET runtime. ET runtime's logic: loop over each backend and it's corresponding backend options, dispatch the backend options to the corresponding backend Next step, will add update API in the method and then module ghstack-source-id: 292255608 @exported-using-ghexport Differential Revision: [D75919242](https://our.internmc.facebook.com/intern/diff/D75919242/)
1 parent a17e86b commit 5d44897

File tree

3 files changed

+329
-0
lines changed

3 files changed

+329
-0
lines changed

runtime/backend/interface.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#include <executorch/runtime/backend/backend_execution_context.h>
1414
#include <executorch/runtime/backend/backend_init_context.h>
15+
#include <executorch/runtime/backend/backend_option_context.h>
16+
#include <executorch/runtime/backend/options.h>
1517
#include <executorch/runtime/core/array_ref.h>
1618
#include <executorch/runtime/core/error.h>
1719
#include <executorch/runtime/core/evalue.h>
@@ -99,6 +101,37 @@ class BackendInterface {
99101
DelegateHandle* handle,
100102
EValue** args) const = 0;
101103

104+
/**
105+
* Responsible update the backend status, if any. The backend options are
106+
* passed in by users, and the backend can update its internal status based on
107+
* the options.
108+
*
109+
* @param[in] context Runtime context if any. Currently it's not used.
110+
* @param[in] args A list of BackendOptions passed in by users.
111+
* @retval Error::Ok if successful.
112+
*/
113+
ET_NODISCARD virtual Error set_option(
114+
__ET_UNUSED BackendOptionContext& context,
115+
const executorch::runtime::Span<BackendOption>& backend_options) {
116+
return Error::Ok;
117+
};
118+
119+
/**
120+
* Responsible update the backend status, if any. The backend options are
121+
* passed in by users, and the backend can update its internal status based on
122+
* the options.
123+
*
124+
* @param[in] context Runtime context if any. Currently it's not used.
125+
* @param[in] args A list of BackendOptions passed in by users, that will be
126+
* filled by the backend
127+
* @retval Error::Ok if successful.
128+
*/
129+
ET_NODISCARD virtual Error get_option(
130+
__ET_UNUSED BackendOptionContext& context,
131+
executorch::runtime::Span<BackendOption>& backend_options) {
132+
return Error::Ok;
133+
};
134+
102135
/**
103136
* Responsible for destroying a handle, if it's required for some backend.
104137
* It may be needed for some backends. For example, resources associated with
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <executorch/runtime/backend/interface.h>
10+
#include <executorch/runtime/platform/runtime.h>
11+
12+
#include <gtest/gtest.h>
13+
14+
using namespace ::testing;
15+
using executorch::runtime::ArrayRef;
16+
using executorch::runtime::Backend;
17+
using executorch::runtime::BackendExecutionContext;
18+
using executorch::runtime::BackendInitContext;
19+
using executorch::runtime::BackendInterface;
20+
using executorch::runtime::BackendOption;
21+
using executorch::runtime::BackendOptionContext;
22+
using executorch::runtime::BackendOptions;
23+
using executorch::runtime::CompileSpec;
24+
using executorch::runtime::DelegateHandle;
25+
using executorch::runtime::Error;
26+
using executorch::runtime::EValue;
27+
using executorch::runtime::FreeableBuffer;
28+
using executorch::runtime::get_backend_class;
29+
using executorch::runtime::MemoryAllocator;
30+
using executorch::runtime::Result;
31+
32+
class MockBackend : public BackendInterface {
33+
public:
34+
~MockBackend() override = default;
35+
36+
bool is_available() const override {
37+
return true;
38+
}
39+
40+
Result<DelegateHandle*> init(
41+
__ET_UNUSED BackendInitContext& context,
42+
__ET_UNUSED FreeableBuffer* processed,
43+
__ET_UNUSED ArrayRef<CompileSpec> compile_specs) const override {
44+
init_called = true;
45+
return nullptr;
46+
}
47+
48+
Error execute(
49+
__ET_UNUSED BackendExecutionContext& context,
50+
__ET_UNUSED DelegateHandle* handle,
51+
__ET_UNUSED EValue** args) const override {
52+
execute_count++;
53+
return Error::Ok;
54+
}
55+
56+
Error set_option(
57+
__ET_UNUSED BackendOptionContext& context,
58+
const executorch::runtime::Span<BackendOption>& backend_options)
59+
override {
60+
set_option_count++;
61+
int success_update = 0;
62+
for (const auto& backend_option : backend_options) {
63+
if (strcmp(backend_option.key, "Backend") == 0) {
64+
if (std::holds_alternative<std::array<char, 256>>(
65+
backend_option.value)) {
66+
// Store the value in our member variable
67+
const auto& arr =
68+
std::get<std::array<char, 256>>(backend_option.value);
69+
target_backend = std::string(arr.data());
70+
success_update++;
71+
}
72+
} else if (strcmp(backend_option.key, "NumberOfThreads") == 0) {
73+
if (std::holds_alternative<int>(backend_option.value)) {
74+
num_threads = std::get<int>(backend_option.value);
75+
success_update++;
76+
}
77+
} else if (strcmp(backend_option.key, "Debug") == 0) {
78+
if (std::holds_alternative<bool>(backend_option.value)) {
79+
debug = std::get<bool>(backend_option.value);
80+
success_update++;
81+
}
82+
}
83+
}
84+
if (success_update == backend_options.size()) {
85+
return Error::Ok;
86+
}
87+
return Error::InvalidArgument;
88+
}
89+
90+
// Mutable allows modification in const methods
91+
mutable std::optional<std::string> target_backend;
92+
mutable int num_threads = 0;
93+
mutable bool debug = false;
94+
95+
// State tracking
96+
mutable bool init_called = false;
97+
mutable int execute_count = 0;
98+
mutable int set_option_count = 0;
99+
};
100+
101+
class BackendInterfaceUpdateTest : public ::testing::Test {
102+
protected:
103+
void SetUp() override {
104+
// Since these tests cause ET_LOG to be called, the PAL must be initialized
105+
// first.
106+
executorch::runtime::runtime_init();
107+
mock_backend = std::make_unique<MockBackend>();
108+
// static Error register_success = register_executor_backend();
109+
}
110+
111+
std::unique_ptr<MockBackend> mock_backend;
112+
BackendOptions<5> options;
113+
};
114+
115+
TEST_F(BackendInterfaceUpdateTest, HandlesInvalidOption) {
116+
BackendOptionContext context;
117+
118+
// Test invalid key case
119+
std::array<char, 256> value_array{"None"};
120+
BackendOption invalid_option{"InvalidKey", value_array};
121+
122+
Error err = mock_backend->set_option(context, invalid_option);
123+
EXPECT_EQ(err, Error::InvalidArgument);
124+
}
125+
126+
TEST_F(BackendInterfaceUpdateTest, HandlesStringOption) {
127+
BackendOptionContext context;
128+
options.set_option("Backend", "GPU");
129+
// // Create a backend option to pass to update
130+
131+
EXPECT_EQ(mock_backend->target_backend, std::nullopt);
132+
133+
// Test successful update
134+
Error err = mock_backend->set_option(context, options.view());
135+
EXPECT_EQ(err, Error::Ok);
136+
137+
EXPECT_EQ(mock_backend->target_backend, "GPU");
138+
}
139+
140+
TEST_F(BackendInterfaceUpdateTest, HandlesIntOption) {
141+
// Check the default num_threads value is 0
142+
EXPECT_EQ(mock_backend->debug, false);
143+
// Create a mock context (needs to be defined or mocked)
144+
BackendOptionContext context;
145+
146+
int expected_num_threads = 4;
147+
148+
// Create a backend option to pass to update
149+
options.set_option("NumberOfThreads", expected_num_threads);
150+
151+
// Test successful update
152+
Error err = mock_backend->set_option(context, options.view());
153+
EXPECT_EQ(err, Error::Ok);
154+
EXPECT_EQ(mock_backend->num_threads, expected_num_threads);
155+
}
156+
157+
TEST_F(BackendInterfaceUpdateTest, HandlesBoolOption) {
158+
// Check the default num_threads value is 0
159+
EXPECT_EQ(mock_backend->debug, false);
160+
// Create a mock context (needs to be defined or mocked)
161+
BackendOptionContext context;
162+
163+
options.set_option("Debug", true);
164+
165+
// Test successful update
166+
Error err = mock_backend->set_option(context, options.view());
167+
EXPECT_EQ(err, Error::Ok);
168+
169+
EXPECT_EQ(mock_backend->debug, true);
170+
}
171+
172+
TEST_F(BackendInterfaceUpdateTest, HandlesMultipleOptions) {
173+
// Check the default num_threads value is 0
174+
EXPECT_EQ(mock_backend->debug, false);
175+
// Create a mock context (needs to be defined or mocked)
176+
BackendOptionContext context;
177+
178+
options.set_option("Debug", true);
179+
options.set_option("NumberOfThreads", 4);
180+
options.set_option("Backend", "GPU");
181+
182+
// Test successful update
183+
Error err = mock_backend->set_option(context, options.view());
184+
EXPECT_EQ(err, Error::Ok);
185+
186+
EXPECT_EQ(mock_backend->debug, true);
187+
EXPECT_EQ(mock_backend->num_threads, 4);
188+
EXPECT_EQ(mock_backend->target_backend, "GPU");
189+
}
190+
191+
TEST_F(BackendInterfaceUpdateTest, UpdateBeforeInit) {
192+
BackendOptionContext option_context;
193+
MemoryAllocator memory_allocator{MemoryAllocator(0, nullptr)};
194+
195+
BackendInitContext init_context(&memory_allocator);
196+
197+
// Create backend option
198+
options.set_option("Backend", "GPU");
199+
200+
// Update before init
201+
Error err = mock_backend->set_option(option_context, options.view());
202+
EXPECT_EQ(err, Error::Ok);
203+
204+
// Now call init
205+
FreeableBuffer* processed = nullptr; // Not used in mock
206+
ArrayRef<CompileSpec> compile_specs; // Empty
207+
auto handle_or_error =
208+
mock_backend->init(init_context, processed, compile_specs);
209+
EXPECT_EQ(handle_or_error.error(), Error::Ok);
210+
211+
// Verify state
212+
EXPECT_TRUE(mock_backend->init_called);
213+
EXPECT_EQ(mock_backend->set_option_count, 1);
214+
EXPECT_EQ(mock_backend->execute_count, 0);
215+
ASSERT_TRUE(mock_backend->target_backend.has_value());
216+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "GPU");
217+
}
218+
219+
TEST_F(BackendInterfaceUpdateTest, UpdateAfterInitBeforeExecute) {
220+
BackendOptionContext option_context;
221+
MemoryAllocator init_memory_allocator{MemoryAllocator(0, nullptr)};
222+
BackendInitContext init_context(&init_memory_allocator);
223+
BackendExecutionContext execute_context;
224+
225+
// First call init
226+
FreeableBuffer* processed = nullptr;
227+
ArrayRef<CompileSpec> compile_specs;
228+
auto handle_or_error =
229+
mock_backend->init(init_context, processed, compile_specs);
230+
EXPECT_TRUE(handle_or_error.ok());
231+
232+
// Verify init called but execute not called
233+
EXPECT_TRUE(mock_backend->init_called);
234+
EXPECT_EQ(mock_backend->execute_count, 0);
235+
236+
// Now update
237+
options.set_option("Backend", "CPU");
238+
Error err = mock_backend->set_option(option_context, options.view());
239+
EXPECT_EQ(err, Error::Ok);
240+
241+
// Now execute
242+
DelegateHandle* handle = handle_or_error.get();
243+
EValue** args = nullptr; // Not used in mock
244+
err = mock_backend->execute(execute_context, handle, args);
245+
EXPECT_EQ(err, Error::Ok);
246+
247+
// Verify state
248+
EXPECT_EQ(mock_backend->set_option_count, 1);
249+
EXPECT_EQ(mock_backend->execute_count, 1);
250+
ASSERT_TRUE(mock_backend->target_backend.has_value());
251+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "CPU");
252+
}
253+
254+
TEST_F(BackendInterfaceUpdateTest, UpdateBetweenExecutes) {
255+
BackendOptionContext option_context;
256+
MemoryAllocator init_memory_allocator{MemoryAllocator(0, nullptr)};
257+
BackendInitContext init_context(&init_memory_allocator);
258+
BackendExecutionContext execute_context;
259+
260+
// Initialize
261+
FreeableBuffer* processed = nullptr;
262+
ArrayRef<CompileSpec> compile_specs;
263+
auto handle_or_error =
264+
mock_backend->init(init_context, processed, compile_specs);
265+
EXPECT_TRUE(handle_or_error.ok());
266+
DelegateHandle* handle = handle_or_error.get();
267+
268+
// First execute
269+
EValue** args = nullptr;
270+
Error err = mock_backend->execute(execute_context, handle, args);
271+
EXPECT_EQ(err, Error::Ok);
272+
273+
// Update between executes
274+
options.set_option("Backend", "NPU");
275+
err = mock_backend->set_option(option_context, options.view());
276+
EXPECT_EQ(err, Error::Ok);
277+
278+
// Second execute
279+
err = mock_backend->execute(execute_context, handle, args);
280+
EXPECT_EQ(err, Error::Ok);
281+
282+
// Verify state
283+
EXPECT_EQ(mock_backend->set_option_count, 1);
284+
EXPECT_EQ(mock_backend->execute_count, 2);
285+
ASSERT_TRUE(mock_backend->target_backend.has_value());
286+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "NPU");
287+
}

runtime/backend/test/targets.bzl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,12 @@ def define_common_targets():
1515
"//executorch/test/utils:utils",
1616
],
1717
)
18+
19+
runtime.cxx_test(
20+
name = "backend_interface_update_test",
21+
srcs = ["backend_interface_update_test.cpp"],
22+
deps = [
23+
"//executorch/runtime/core:core",
24+
"//executorch/runtime/backend:interface",
25+
],
26+
)

0 commit comments

Comments
 (0)