Skip to content

Commit c25128c

Browse files
committed
[3/N] Add update function in backend interface
Add update function in backend interface class. Next step, will add update API in the method and then module Differential Revision: [D75919242](https://our.internmc.facebook.com/intern/diff/D75919242/) ghstack-source-id: 288333606 Pull Request resolved: #11391
1 parent 0de6b2b commit c25128c

File tree

4 files changed

+315
-0
lines changed

4 files changed

+315
-0
lines changed

runtime/backend/backend_options.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <cstddef>
1010
#include <cstring>
1111
#include <executorch/runtime/core/error.h>
12+
#include <executorch/runtime/core/array_ref.h>
1213

1314
namespace executorch {
1415
namespace runtime {
@@ -117,6 +118,10 @@ class BackendOptions {
117118
return err;
118119
}
119120

121+
executorch::runtime::ArrayRef<BackendOption> view() const {
122+
return executorch::runtime::ArrayRef<BackendOption>(options, size);
123+
}
124+
120125
private:
121126
BackendOption options[MaxCapacity]{}; // Storage for options
122127
size_t size; // Current number of options

runtime/backend/interface.h

Lines changed: 14 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_update_context.h>
16+
#include <executorch/runtime/backend/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,18 @@ class BackendInterface {
99101
DelegateHandle* handle,
100102
EValue** args) const = 0;
101103

104+
/**
105+
* Responsible update the backend status, if any. The backend options are passed in
106+
* by users, and the backend can update its internal status based on the options.
107+
*
108+
* @param[in] context Runtime context if any. Currently it's not used.
109+
* @param[in] args A list of BackendOptions passed in by users.
110+
* @retval Error::Ok if successful.
111+
*/
112+
ET_NODISCARD virtual Error update(
113+
BackendUpdateContext& context,
114+
const executorch::runtime::ArrayRef<BackendOption>& backend_options) const = 0;
115+
102116
/**
103117
* Responsible for destroying a handle, if it's required for some backend.
104118
* 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::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::BackendUpdateContext;
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::OptionType;
34+
using executorch::runtime::MemoryAllocator;
35+
36+
class MockBackend : public BackendInterface {
37+
public:
38+
~MockBackend() override = default;
39+
40+
bool is_available() const override { return true; }
41+
42+
Result<DelegateHandle*> init(
43+
BackendInitContext& context,
44+
FreeableBuffer* processed,
45+
ArrayRef<CompileSpec> compile_specs) const override {
46+
init_called = true;
47+
return nullptr;
48+
}
49+
50+
Error execute(
51+
BackendExecutionContext& context,
52+
DelegateHandle* handle,
53+
EValue** args) const override {
54+
execute_count++;
55+
return Error::Ok;
56+
}
57+
58+
Error update(
59+
BackendUpdateContext& context,
60+
const executorch::runtime::ArrayRef<BackendOption>& backend_options) const override {
61+
update_count++;
62+
int sucess_update = 0;
63+
for (const auto& backend_option : backend_options) {
64+
if (strcmp(backend_option.key, "Backend") == 0) {
65+
if (backend_option.type == OptionType::STRING) {
66+
// Store the value in our member variable
67+
target_backend = backend_option.value.string_value;
68+
sucess_update++;
69+
}
70+
} else if (strcmp(backend_option.key, "NumberOfThreads") == 0) {
71+
if (backend_option.type == OptionType::INT) {
72+
num_threads = backend_option.value.int_value;
73+
sucess_update++;
74+
}
75+
} else if (strcmp(backend_option.key, "Debug") == 0) {
76+
if (backend_option.type == OptionType::BOOL) {
77+
debug = backend_option.value.bool_value;
78+
sucess_update++;
79+
}
80+
}
81+
}
82+
if (sucess_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 update_count = 0;
97+
};
98+
99+
class BackendInterfaceUpdateTest : public ::testing::Test {
100+
protected:
101+
102+
void SetUp() override {
103+
// Since these tests cause ET_LOG to be called, the PAL must be initialized
104+
// first.
105+
executorch::runtime::runtime_init();
106+
mock_backend = std::make_unique<MockBackend>();
107+
// static Error register_success = register_executor_backend();
108+
}
109+
110+
std::unique_ptr<MockBackend> mock_backend;
111+
BackendOptions<5> options;
112+
};
113+
114+
TEST_F(BackendInterfaceUpdateTest, HandlesInvalidOption) {
115+
BackendUpdateContext context;
116+
117+
// Test invalid key case
118+
BackendOption invalid_option{
119+
"InvalidKey",
120+
OptionType::STRING,
121+
{.string_value = "None"}
122+
};
123+
124+
Error err = mock_backend->update(context, invalid_option);
125+
EXPECT_EQ(err, Error::InvalidArgument);
126+
127+
}
128+
129+
TEST_F(BackendInterfaceUpdateTest, HandlesStringOption) {
130+
BackendUpdateContext context;
131+
options.set_option(StrKey("Backend"), "GPU");
132+
// // Create a backend option to pass to update
133+
134+
EXPECT_EQ(mock_backend->target_backend, std::nullopt);
135+
136+
// Test successful update
137+
Error err = mock_backend->update(context, options.view());
138+
EXPECT_EQ(err, Error::Ok);
139+
140+
EXPECT_EQ(mock_backend->target_backend, "GPU");
141+
}
142+
143+
TEST_F(BackendInterfaceUpdateTest, HandlesIntOption) {
144+
// Check the default num_threads value is 0
145+
EXPECT_EQ(mock_backend->debug, false);
146+
// Create a mock context (needs to be defined or mocked)
147+
BackendUpdateContext context;
148+
149+
int expected_num_threads = 4;
150+
151+
// Create a backend option to pass to update
152+
options.set_option(IntKey("NumberOfThreads"), expected_num_threads);
153+
154+
// Test successful update
155+
Error err = mock_backend->update(context, options.view());
156+
EXPECT_EQ(err, Error::Ok);
157+
EXPECT_EQ(mock_backend->num_threads, expected_num_threads);
158+
}
159+
160+
TEST_F(BackendInterfaceUpdateTest, HandlesBoolOption) {
161+
// Check the default num_threads value is 0
162+
EXPECT_EQ(mock_backend->debug, false);
163+
// Create a mock context (needs to be defined or mocked)
164+
BackendUpdateContext context;
165+
166+
options.set_option(BoolKey("Debug"), true);
167+
168+
// Test successful update
169+
Error err = mock_backend->update(context, options.view());
170+
EXPECT_EQ(err, Error::Ok);
171+
172+
EXPECT_EQ(mock_backend->debug, true);
173+
}
174+
175+
TEST_F(BackendInterfaceUpdateTest, HandlesMultipleOptions) {
176+
// Check the default num_threads value is 0
177+
EXPECT_EQ(mock_backend->debug, false);
178+
// Create a mock context (needs to be defined or mocked)
179+
BackendUpdateContext context;
180+
181+
options.set_option(BoolKey("Debug"), true);
182+
options.set_option(IntKey("NumberOfThreads"), 4);
183+
options.set_option(StrKey("Backend"), "GPU");
184+
185+
// Test successful update
186+
Error err = mock_backend->update(context, options.view());
187+
EXPECT_EQ(err, Error::Ok);
188+
189+
EXPECT_EQ(mock_backend->debug, true);
190+
EXPECT_EQ(mock_backend->num_threads, 4);
191+
EXPECT_EQ(mock_backend->target_backend, "GPU");
192+
}
193+
194+
TEST_F(BackendInterfaceUpdateTest, UpdateBeforeInit) {
195+
BackendUpdateContext update_context;
196+
MemoryAllocator memory_allocator{MemoryAllocator(0, nullptr)};
197+
198+
BackendInitContext init_context(&memory_allocator);
199+
200+
// Create backend option
201+
options.set_option(StrKey("Backend"), "GPU");
202+
203+
// Update before init
204+
Error err = mock_backend->update(update_context, options.view());
205+
EXPECT_EQ(err, Error::Ok);
206+
207+
// Now call init
208+
FreeableBuffer* processed = nullptr; // Not used in mock
209+
ArrayRef<CompileSpec> compile_specs; // Empty
210+
auto handle_or_error = mock_backend->init(init_context, processed, compile_specs);
211+
EXPECT_EQ(handle_or_error.error(), Error::Ok);
212+
213+
// Verify state
214+
EXPECT_TRUE(mock_backend->init_called);
215+
EXPECT_EQ(mock_backend->update_count, 1);
216+
EXPECT_EQ(mock_backend->execute_count, 0);
217+
ASSERT_TRUE(mock_backend->target_backend.has_value());
218+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "GPU");
219+
}
220+
221+
TEST_F(BackendInterfaceUpdateTest, UpdateAfterInitBeforeExecute) {
222+
BackendUpdateContext update_context;
223+
MemoryAllocator init_memory_allocator{MemoryAllocator(0, nullptr)};
224+
BackendInitContext init_context(&init_memory_allocator);
225+
BackendExecutionContext execute_context;
226+
227+
// First call init
228+
FreeableBuffer* processed = nullptr;
229+
ArrayRef<CompileSpec> compile_specs;
230+
auto handle_or_error = mock_backend->init(init_context, processed, compile_specs);
231+
EXPECT_TRUE(handle_or_error.ok());
232+
233+
// Verify init called but execute not called
234+
EXPECT_TRUE(mock_backend->init_called);
235+
EXPECT_EQ(mock_backend->execute_count, 0);
236+
237+
// Now update
238+
options.set_option(StrKey("Backend"), "CPU");
239+
Error err = mock_backend->update(update_context, options.view());
240+
EXPECT_EQ(err, Error::Ok);
241+
242+
// Now execute
243+
DelegateHandle* handle = handle_or_error.get();
244+
EValue** args = nullptr; // Not used in mock
245+
err = mock_backend->execute(execute_context, handle, args);
246+
EXPECT_EQ(err, Error::Ok);
247+
248+
// Verify state
249+
EXPECT_EQ(mock_backend->update_count, 1);
250+
EXPECT_EQ(mock_backend->execute_count, 1);
251+
ASSERT_TRUE(mock_backend->target_backend.has_value());
252+
EXPECT_STREQ(mock_backend->target_backend.value().c_str(), "CPU");
253+
}
254+
255+
TEST_F(BackendInterfaceUpdateTest, UpdateBetweenExecutes) {
256+
BackendUpdateContext update_context;
257+
MemoryAllocator init_memory_allocator{MemoryAllocator(0, nullptr)};
258+
BackendInitContext init_context(&init_memory_allocator);
259+
BackendExecutionContext execute_context;
260+
261+
// Initialize
262+
FreeableBuffer* processed = nullptr;
263+
ArrayRef<CompileSpec> compile_specs;
264+
auto handle_or_error = 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(StrKey("Backend"), "NPU");
275+
err = mock_backend->update(update_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->update_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
@@ -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)