Skip to content

Commit 8e21f20

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 a2ebd0a commit 8e21f20

File tree

3 files changed

+326
-0
lines changed

3 files changed

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

runtime/backend/test/targets.bzl

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

0 commit comments

Comments
 (0)