Skip to content

Commit 0a016e8

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: 290059231 @exported-using-ghexport Differential Revision: [D75919242](https://our.internmc.facebook.com/intern/diff/D75919242/)
1 parent d1c8b00 commit 0a016e8

File tree

3 files changed

+327
-0
lines changed

3 files changed

+327
-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/options.h>
16+
#include <executorch/runtime/backend/backend_option_context.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+
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+
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: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
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+
BackendInitContext& context,
42+
FreeableBuffer* processed,
43+
ArrayRef<CompileSpec> compile_specs) const override {
44+
init_called = true;
45+
return nullptr;
46+
}
47+
48+
Error execute(
49+
BackendExecutionContext& context,
50+
DelegateHandle* handle,
51+
EValue** args) const override {
52+
execute_count++;
53+
return Error::Ok;
54+
}
55+
56+
Error set_option(
57+
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>>(backend_option.value)) {
65+
// Store the value in our member variable
66+
const auto& arr = std::get<std::array<char, 256>>(backend_option.value);
67+
target_backend = std::string(arr.data());
68+
success_update++;
69+
}
70+
} else if (strcmp(backend_option.key, "NumberOfThreads") == 0) {
71+
if (std::holds_alternative<int>(backend_option.value)) {
72+
num_threads = std::get<int>(backend_option.value);
73+
success_update++;
74+
}
75+
} else if (strcmp(backend_option.key, "Debug") == 0) {
76+
if (std::holds_alternative<bool>(backend_option.value)) {
77+
debug = std::get<bool>(backend_option.value);
78+
success_update++;
79+
}
80+
}
81+
}
82+
if (success_update == backend_options.size()) {
83+
return Error::Ok;
84+
}
85+
return Error::InvalidArgument;
86+
}
87+
88+
// Mutable allows modification in const methods
89+
mutable std::optional<std::string> target_backend;
90+
mutable int num_threads = 0;
91+
mutable bool debug = false;
92+
93+
// State tracking
94+
mutable bool init_called = false;
95+
mutable int execute_count = 0;
96+
mutable int set_option_count = 0;
97+
};
98+
99+
class BackendInterfaceUpdateTest : public ::testing::Test {
100+
protected:
101+
void SetUp() override {
102+
// Since these tests cause ET_LOG to be called, the PAL must be initialized
103+
// first.
104+
executorch::runtime::runtime_init();
105+
mock_backend = std::make_unique<MockBackend>();
106+
// static Error register_success = register_executor_backend();
107+
}
108+
109+
std::unique_ptr<MockBackend> mock_backend;
110+
BackendOptions<5> options;
111+
};
112+
113+
TEST_F(BackendInterfaceUpdateTest, HandlesInvalidOption) {
114+
BackendOptionContext context;
115+
116+
// Test invalid key case
117+
std::array<char, 256> value_array{"None"};
118+
BackendOption invalid_option{"InvalidKey", value_array};
119+
120+
Error err = mock_backend->set_option(context, invalid_option);
121+
EXPECT_EQ(err, Error::InvalidArgument);
122+
}
123+
124+
TEST_F(BackendInterfaceUpdateTest, HandlesStringOption) {
125+
BackendOptionContext context;
126+
options.set_option("Backend", "GPU");
127+
// // Create a backend option to pass to update
128+
129+
EXPECT_EQ(mock_backend->target_backend, std::nullopt);
130+
131+
// Test successful update
132+
Error err = mock_backend->set_option(context, options.view());
133+
EXPECT_EQ(err, Error::Ok);
134+
135+
EXPECT_EQ(mock_backend->target_backend, "GPU");
136+
}
137+
138+
TEST_F(BackendInterfaceUpdateTest, HandlesIntOption) {
139+
// Check the default num_threads value is 0
140+
EXPECT_EQ(mock_backend->debug, false);
141+
// Create a mock context (needs to be defined or mocked)
142+
BackendOptionContext context;
143+
144+
int expected_num_threads = 4;
145+
146+
// Create a backend option to pass to update
147+
options.set_option("NumberOfThreads", expected_num_threads);
148+
149+
// Test successful update
150+
Error err = mock_backend->set_option(context, options.view());
151+
EXPECT_EQ(err, Error::Ok);
152+
EXPECT_EQ(mock_backend->num_threads, expected_num_threads);
153+
}
154+
155+
TEST_F(BackendInterfaceUpdateTest, HandlesBoolOption) {
156+
// Check the default num_threads value is 0
157+
EXPECT_EQ(mock_backend->debug, false);
158+
// Create a mock context (needs to be defined or mocked)
159+
BackendOptionContext context;
160+
161+
options.set_option("Debug", true);
162+
163+
// Test successful update
164+
Error err = mock_backend->set_option(context, options.view());
165+
EXPECT_EQ(err, Error::Ok);
166+
167+
EXPECT_EQ(mock_backend->debug, true);
168+
}
169+
170+
TEST_F(BackendInterfaceUpdateTest, HandlesMultipleOptions) {
171+
// Check the default num_threads value is 0
172+
EXPECT_EQ(mock_backend->debug, false);
173+
// Create a mock context (needs to be defined or mocked)
174+
BackendOptionContext context;
175+
176+
options.set_option("Debug", true);
177+
options.set_option("NumberOfThreads", 4);
178+
options.set_option("Backend", "GPU");
179+
180+
// Test successful update
181+
Error err = mock_backend->set_option(context, options.view());
182+
EXPECT_EQ(err, Error::Ok);
183+
184+
EXPECT_EQ(mock_backend->debug, true);
185+
EXPECT_EQ(mock_backend->num_threads, 4);
186+
EXPECT_EQ(mock_backend->target_backend, "GPU");
187+
}
188+
189+
TEST_F(BackendInterfaceUpdateTest, UpdateBeforeInit) {
190+
BackendOptionContext option_context;
191+
MemoryAllocator memory_allocator{MemoryAllocator(0, nullptr)};
192+
193+
BackendInitContext init_context(&memory_allocator);
194+
195+
// Create backend option
196+
options.set_option("Backend", "GPU");
197+
198+
// Update before init
199+
Error err = mock_backend->set_option(option_context, options.view());
200+
EXPECT_EQ(err, Error::Ok);
201+
202+
// Now call init
203+
FreeableBuffer* processed = nullptr; // Not used in mock
204+
ArrayRef<CompileSpec> compile_specs; // Empty
205+
auto handle_or_error =
206+
mock_backend->init(init_context, processed, compile_specs);
207+
EXPECT_EQ(handle_or_error.error(), Error::Ok);
208+
209+
// Verify state
210+
EXPECT_TRUE(mock_backend->init_called);
211+
EXPECT_EQ(mock_backend->set_option_count, 1);
212+
EXPECT_EQ(mock_backend->execute_count, 0);
213+
ASSERT_TRUE(mock_backend->target_backend.has_value());
214+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "GPU");
215+
}
216+
217+
TEST_F(BackendInterfaceUpdateTest, UpdateAfterInitBeforeExecute) {
218+
BackendOptionContext option_context;
219+
MemoryAllocator init_memory_allocator{MemoryAllocator(0, nullptr)};
220+
BackendInitContext init_context(&init_memory_allocator);
221+
BackendExecutionContext execute_context;
222+
223+
// First call init
224+
FreeableBuffer* processed = nullptr;
225+
ArrayRef<CompileSpec> compile_specs;
226+
auto handle_or_error =
227+
mock_backend->init(init_context, processed, compile_specs);
228+
EXPECT_TRUE(handle_or_error.ok());
229+
230+
// Verify init called but execute not called
231+
EXPECT_TRUE(mock_backend->init_called);
232+
EXPECT_EQ(mock_backend->execute_count, 0);
233+
234+
// Now update
235+
options.set_option("Backend", "CPU");
236+
Error err = mock_backend->set_option(option_context, options.view());
237+
EXPECT_EQ(err, Error::Ok);
238+
239+
// Now execute
240+
DelegateHandle* handle = handle_or_error.get();
241+
EValue** args = nullptr; // Not used in mock
242+
err = mock_backend->execute(execute_context, handle, args);
243+
EXPECT_EQ(err, Error::Ok);
244+
245+
// Verify state
246+
EXPECT_EQ(mock_backend->set_option_count, 1);
247+
EXPECT_EQ(mock_backend->execute_count, 1);
248+
ASSERT_TRUE(mock_backend->target_backend.has_value());
249+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "CPU");
250+
}
251+
252+
TEST_F(BackendInterfaceUpdateTest, UpdateBetweenExecutes) {
253+
BackendOptionContext option_context;
254+
MemoryAllocator init_memory_allocator{MemoryAllocator(0, nullptr)};
255+
BackendInitContext init_context(&init_memory_allocator);
256+
BackendExecutionContext execute_context;
257+
258+
// Initialize
259+
FreeableBuffer* processed = nullptr;
260+
ArrayRef<CompileSpec> compile_specs;
261+
auto handle_or_error =
262+
mock_backend->init(init_context, processed, compile_specs);
263+
EXPECT_TRUE(handle_or_error.ok());
264+
DelegateHandle* handle = handle_or_error.get();
265+
266+
// First execute
267+
EValue** args = nullptr;
268+
Error err = mock_backend->execute(execute_context, handle, args);
269+
EXPECT_EQ(err, Error::Ok);
270+
271+
// Update between executes
272+
options.set_option("Backend", "NPU");
273+
err = mock_backend->set_option(option_context, options.view());
274+
EXPECT_EQ(err, Error::Ok);
275+
276+
// Second execute
277+
err = mock_backend->execute(execute_context, handle, args);
278+
EXPECT_EQ(err, Error::Ok);
279+
280+
// Verify state
281+
EXPECT_EQ(mock_backend->set_option_count, 1);
282+
EXPECT_EQ(mock_backend->execute_count, 2);
283+
ASSERT_TRUE(mock_backend->target_backend.has_value());
284+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "NPU");
285+
}

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)