Skip to content

Commit 863971b

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 652de89 commit 863971b

File tree

3 files changed

+328
-0
lines changed

3 files changed

+328
-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: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
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::BackendInterface;
16+
using executorch::runtime::Result;
17+
using executorch::runtime::DelegateHandle;
18+
using executorch::runtime::FreeableBuffer;
19+
using executorch::runtime::BackendInitContext;
20+
using executorch::runtime::CompileSpec;
21+
using executorch::runtime::ArrayRef;
22+
using executorch::runtime::Error;
23+
using executorch::runtime::BackendExecutionContext;
24+
using executorch::runtime::EValue;
25+
using executorch::runtime::BackendOptionContext;
26+
using executorch::runtime::BackendOption;
27+
using executorch::runtime::BackendOptions;
28+
using executorch::runtime::Backend;
29+
using executorch::runtime::StrKey;
30+
using executorch::runtime::IntKey;
31+
using executorch::runtime::BoolKey;
32+
using executorch::runtime::get_backend_class;
33+
using executorch::runtime::MemoryAllocator;
34+
35+
class MockBackend : public BackendInterface {
36+
public:
37+
~MockBackend() override = default;
38+
39+
bool is_available() const override { return true; }
40+
41+
Result<DelegateHandle*> init(
42+
BackendInitContext& context,
43+
FreeableBuffer* processed,
44+
ArrayRef<CompileSpec> compile_specs) const override {
45+
init_called = true;
46+
return nullptr;
47+
}
48+
49+
Error execute(
50+
BackendExecutionContext& context,
51+
DelegateHandle* handle,
52+
EValue** args) const override {
53+
execute_count++;
54+
return Error::Ok;
55+
}
56+
57+
Error set_option(
58+
BackendOptionContext& context,
59+
const executorch::runtime::Span<BackendOption>& backend_options) 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+
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+
BackendOption invalid_option{
118+
"InvalidKey",
119+
"None"
120+
};
121+
122+
// Create a span from the single option
123+
Error err = mock_backend->set_option(context, invalid_option);
124+
EXPECT_EQ(err, Error::InvalidArgument);
125+
126+
}
127+
128+
TEST_F(BackendInterfaceUpdateTest, HandlesStringOption) {
129+
BackendOptionContext context;
130+
options.set_option(StrKey("Backend"), "GPU");
131+
// // Create a backend option to pass to update
132+
133+
EXPECT_EQ(mock_backend->target_backend, std::nullopt);
134+
135+
// Test successful update
136+
Error err = mock_backend->set_option(context, options.view());
137+
EXPECT_EQ(err, Error::Ok);
138+
139+
EXPECT_EQ(mock_backend->target_backend, "GPU");
140+
}
141+
142+
TEST_F(BackendInterfaceUpdateTest, HandlesIntOption) {
143+
// Check the default num_threads value is 0
144+
EXPECT_EQ(mock_backend->debug, false);
145+
// Create a mock context (needs to be defined or mocked)
146+
BackendOptionContext context;
147+
148+
int expected_num_threads = 4;
149+
150+
// Create a backend option to pass to update
151+
options.set_option(IntKey("NumberOfThreads"), expected_num_threads);
152+
153+
// Test successful update
154+
Error err = mock_backend->set_option(context, options.view());
155+
EXPECT_EQ(err, Error::Ok);
156+
EXPECT_EQ(mock_backend->num_threads, expected_num_threads);
157+
}
158+
159+
TEST_F(BackendInterfaceUpdateTest, HandlesBoolOption) {
160+
// Check the default num_threads value is 0
161+
EXPECT_EQ(mock_backend->debug, false);
162+
// Create a mock context (needs to be defined or mocked)
163+
BackendOptionContext context;
164+
165+
options.set_option(BoolKey("Debug"), true);
166+
167+
// Test successful update
168+
Error err = mock_backend->set_option(context, options.view());
169+
EXPECT_EQ(err, Error::Ok);
170+
171+
EXPECT_EQ(mock_backend->debug, true);
172+
}
173+
174+
TEST_F(BackendInterfaceUpdateTest, HandlesMultipleOptions) {
175+
// Check the default num_threads value is 0
176+
EXPECT_EQ(mock_backend->debug, false);
177+
// Create a mock context (needs to be defined or mocked)
178+
BackendOptionContext context;
179+
180+
options.set_option(BoolKey("Debug"), true);
181+
options.set_option(IntKey("NumberOfThreads"), 4);
182+
options.set_option(StrKey("Backend"), "GPU");
183+
184+
// Test successful update
185+
Error err = mock_backend->set_option(context, options.view());
186+
EXPECT_EQ(err, Error::Ok);
187+
188+
EXPECT_EQ(mock_backend->debug, true);
189+
EXPECT_EQ(mock_backend->num_threads, 4);
190+
EXPECT_EQ(mock_backend->target_backend, "GPU");
191+
}
192+
193+
TEST_F(BackendInterfaceUpdateTest, UpdateBeforeInit) {
194+
BackendOptionContext option_context;
195+
MemoryAllocator memory_allocator{MemoryAllocator(0, nullptr)};
196+
197+
BackendInitContext init_context(&memory_allocator);
198+
199+
// Create backend option
200+
options.set_option(StrKey("Backend"), "GPU");
201+
202+
// Update before init
203+
Error err = mock_backend->set_option(option_context, options.view());
204+
EXPECT_EQ(err, Error::Ok);
205+
206+
// Now call init
207+
FreeableBuffer* processed = nullptr; // Not used in mock
208+
ArrayRef<CompileSpec> compile_specs; // Empty
209+
auto handle_or_error = mock_backend->init(init_context, processed, compile_specs);
210+
EXPECT_EQ(handle_or_error.error(), Error::Ok);
211+
212+
// Verify state
213+
EXPECT_TRUE(mock_backend->init_called);
214+
EXPECT_EQ(mock_backend->set_option_count, 1);
215+
EXPECT_EQ(mock_backend->execute_count, 0);
216+
ASSERT_TRUE(mock_backend->target_backend.has_value());
217+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "GPU");
218+
}
219+
220+
TEST_F(BackendInterfaceUpdateTest, UpdateAfterInitBeforeExecute) {
221+
BackendOptionContext option_context;
222+
MemoryAllocator init_memory_allocator{MemoryAllocator(0, nullptr)};
223+
BackendInitContext init_context(&init_memory_allocator);
224+
BackendExecutionContext execute_context;
225+
226+
// First call init
227+
FreeableBuffer* processed = nullptr;
228+
ArrayRef<CompileSpec> compile_specs;
229+
auto handle_or_error = 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(StrKey("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 = mock_backend->init(init_context, processed, compile_specs);
264+
EXPECT_TRUE(handle_or_error.ok());
265+
DelegateHandle* handle = handle_or_error.get();
266+
267+
// First execute
268+
EValue** args = nullptr;
269+
Error err = mock_backend->execute(execute_context, handle, args);
270+
EXPECT_EQ(err, Error::Ok);
271+
272+
// Update between executes
273+
options.set_option(StrKey("Backend"), "NPU");
274+
err = mock_backend->set_option(option_context, options.view());
275+
EXPECT_EQ(err, Error::Ok);
276+
277+
// Second execute
278+
err = mock_backend->execute(execute_context, handle, args);
279+
EXPECT_EQ(err, Error::Ok);
280+
281+
// Verify state
282+
EXPECT_EQ(mock_backend->set_option_count, 1);
283+
EXPECT_EQ(mock_backend->execute_count, 2);
284+
ASSERT_TRUE(mock_backend->target_backend.has_value());
285+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "NPU");
286+
}

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)