Skip to content

Commit af7b35f

Browse files
committed
[5/N] Add get_option/set_option APIs
Pull Request resolved: #11758 ghstack-source-id: 290994800 Expose the API to set/get backend option. We can either pass in {backend_name, backend options} or {backend options map} Differential Revision: [D76825663](https://our.internmc.facebook.com/intern/diff/D76825663/)
1 parent 263ce7e commit af7b35f

File tree

3 files changed

+267
-0
lines changed

3 files changed

+267
-0
lines changed

runtime/backend/options_map.h

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <executorch/runtime/backend/options.h>
1212
#include <executorch/runtime/core/error.h>
1313
#include <executorch/runtime/core/span.h>
14+
#include <executorch/runtime/backend/backend_option_context.h>
15+
#include <executorch/runtime/backend/interface.h>
1416
#include <cstring>
1517
namespace executorch {
1618
namespace runtime {
@@ -82,5 +84,106 @@ class BackendOptionsMap {
8284
size_t size_ = 0; // Current number of entries
8385
};
8486

87+
88+
/**
89+
* Retrieves backend options for a specific backend.
90+
*
91+
* @param backend_name The name of the backend to get options from
92+
* @param backend_options The backend option objects that will be filled with
93+
* the populated values from the backend
94+
* @return Error::Ok on success, Error::NotFound if backend is not found, or
95+
* other error codes on failure
96+
*/
97+
Error get_option(
98+
const char* backend_name,
99+
executorch::runtime::Span<executorch::runtime::BackendOption>
100+
backend_options) {
101+
auto backend_class = get_backend_class(backend_name);
102+
if (!backend_class) {
103+
return Error::NotFound;
104+
}
105+
executorch::runtime::BackendOptionContext backend_option_context;
106+
executorch::runtime::Span<BackendOption> backend_options_ref(
107+
backend_options.data(), backend_options.size());
108+
auto result =
109+
backend_class->get_option(backend_option_context, backend_options_ref);
110+
if (result != Error::Ok) {
111+
return result;
112+
}
113+
return Error::Ok;
114+
}
115+
116+
/**
117+
* Retrieves backend options for multiple backends using a backend options map.
118+
*
119+
* @param backend_options_map The backend option map containing backend names
120+
* and their associated options, which will be filled with the populated values
121+
* from the backend
122+
* @return Error::Ok on success, or the first error encountered when processing
123+
* the entries
124+
*/
125+
Error get_option(
126+
executorch::runtime::Span<executorch::runtime::Entry> backend_options_map) {
127+
Error result = Error::Ok;
128+
for (auto& entry : backend_options_map) {
129+
const char* backend_name = entry.backend_name;
130+
auto backend_options = entry.options;
131+
auto result = get_option(backend_name, backend_options);
132+
if (result != Error::Ok) {
133+
return result;
134+
}
135+
}
136+
return Error::Ok;
137+
}
138+
139+
/**
140+
* Sets backend options for a specific backend.
141+
*
142+
* @param backend_name The name of the backend to set options for
143+
* @param backend_options The backend option list containing the options
144+
* to set
145+
* @return Error::Ok on success, Error::NotFound if backend is not found, or
146+
* other error codes on failure
147+
*/
148+
Error set_option(
149+
const char* backend_name,
150+
const executorch::runtime::Span<executorch::runtime::BackendOption>
151+
backend_options) {
152+
auto backend_class = get_backend_class(backend_name);
153+
if (!backend_class) {
154+
return Error::NotFound;
155+
}
156+
157+
executorch::runtime::BackendOptionContext backend_option_context;
158+
Error result =
159+
backend_class->set_option(backend_option_context, backend_options);
160+
if (result != Error::Ok) {
161+
return result;
162+
}
163+
return Error::Ok;
164+
}
165+
166+
/**
167+
* Sets backend options for multiple backends using a backend options map.
168+
*
169+
* @param backend_options_map The backend option map containing backend names
170+
* and their associated backend options to set
171+
* @return Error::Ok on success, or the first error encountered when processing
172+
*/
173+
Error set_option(const executorch::runtime::Span<executorch::runtime::Entry>
174+
backend_options_map) {
175+
Error result = Error::Ok;
176+
for (const auto& entry : backend_options_map) {
177+
const char* backend_name = entry.backend_name;
178+
auto backend_options = entry.options;
179+
result = set_option(backend_name, backend_options);
180+
181+
if (result != Error::Ok) {
182+
return result;
183+
}
184+
}
185+
return Error::Ok;
186+
}
187+
85188
} // namespace runtime
86189
} // namespace executorch

runtime/backend/targets.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ def define_common_targets():
6868
exported_deps = [
6969
"//executorch/runtime/core:core",
7070
":options" + aten_suffix,
71+
":interface" + aten_suffix,
7172
],
7273
)

runtime/backend/test/backend_options_map_test.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,31 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9+
#include <executorch/runtime/backend/interface.h>
910
#include <executorch/runtime/backend/options.h>
1011
#include <executorch/runtime/backend/options_map.h>
1112
#include <executorch/runtime/platform/runtime.h>
1213

1314
#include <gtest/gtest.h>
1415

1516
using namespace ::testing;
17+
using executorch::runtime::ArrayRef;
18+
using executorch::runtime::Backend;
19+
using executorch::runtime::BackendExecutionContext;
20+
using executorch::runtime::BackendInitContext;
21+
using executorch::runtime::BackendInterface;
1622
using executorch::runtime::BackendOption;
23+
using executorch::runtime::BackendOptionContext;
1724
using executorch::runtime::BackendOptions;
1825
using executorch::runtime::BackendOptionsMap;
26+
using executorch::runtime::CompileSpec;
27+
using executorch::runtime::DelegateHandle;
1928
using executorch::runtime::Error;
29+
using executorch::runtime::EValue;
30+
using executorch::runtime::FreeableBuffer;
2031
using executorch::runtime::OptionKey;
32+
using executorch::runtime::register_backend;
33+
using executorch::runtime::Result;
2134

2235
namespace executorch {
2336
namespace runtime {
@@ -144,5 +157,155 @@ TEST_F(BackendOptionsMapTest, OptionIsolation) {
144157
EXPECT_STREQ(gpu_opts[2].key, "Hardware");
145158
EXPECT_STREQ(std::get<const char*>(gpu_opts[2].value), "H100");
146159
}
160+
161+
// Mock backend for testing
162+
class StubBackend : public BackendInterface {
163+
public:
164+
~StubBackend() override = default;
165+
166+
bool is_available() const override {
167+
return true;
168+
}
169+
170+
Result<DelegateHandle*> init(
171+
BackendInitContext& context,
172+
FreeableBuffer* processed,
173+
ArrayRef<CompileSpec> compile_specs) const override {
174+
return nullptr;
175+
}
176+
177+
Error execute(
178+
BackendExecutionContext& context,
179+
DelegateHandle* handle,
180+
EValue** args) const override {
181+
return Error::Ok;
182+
}
183+
184+
Error get_option(
185+
BackendOptionContext& context,
186+
executorch::runtime::Span<executorch::runtime::BackendOption>&
187+
backend_options) override {
188+
// For testing purposes, just record that get_option was called
189+
// and verify the input parameters
190+
get_option_called = true;
191+
get_option_call_count++;
192+
last_get_option_size = backend_options.size();
193+
194+
// Verify that the expected option key is present and modify the value
195+
for (size_t i = 0; i < backend_options.size(); ++i) {
196+
if (strcmp(backend_options[i].key, "NumberOfThreads") == 0) {
197+
// Set the value to what was stored by set_option
198+
backend_options[i].value = last_num_threads;
199+
found_expected_key = true;
200+
break;
201+
}
202+
}
203+
204+
return Error::Ok;
205+
}
206+
207+
Error set_option(
208+
BackendOptionContext& context,
209+
const Span<executorch::runtime::BackendOption>& backend_options)
210+
override {
211+
// Store the options for verification
212+
last_options_size = backend_options.size();
213+
if (backend_options.size() > 0) {
214+
for (const auto& option : backend_options) {
215+
if (strcmp(option.key, "NumberOfThreads") == 0) {
216+
if (auto* val = std::get_if<int>(&option.value)) {
217+
last_num_threads = *val;
218+
}
219+
}
220+
}
221+
}
222+
return Error::Ok;
223+
}
224+
225+
// Mutable for testing verification
226+
size_t last_options_size = 0;
227+
int last_num_threads = 0;
228+
bool get_option_called = false;
229+
int get_option_call_count = 0;
230+
size_t last_get_option_size = 0;
231+
bool found_expected_key = false;
232+
};
233+
234+
class BackendUpdateTest : public ::testing::Test {
235+
protected:
236+
void SetUp() override {
237+
// Since these tests cause ET_LOG to be called, the PAL must be initialized
238+
// first.
239+
executorch::runtime::runtime_init();
240+
241+
// Register the stub backend
242+
stub_backend = std::make_unique<StubBackend>();
243+
Backend backend_config{"StubBackend", stub_backend.get()};
244+
auto register_result = register_backend(backend_config);
245+
ASSERT_EQ(register_result, Error::Ok);
246+
}
247+
248+
std::unique_ptr<StubBackend> stub_backend;
249+
};
250+
251+
// Test basic string functionality
252+
TEST_F(BackendUpdateTest, TestSetOption) {
253+
BackendOptionsMap<3> map;
254+
BackendOptions<1> backend_options;
255+
int new_num_threads = 4;
256+
backend_options.set_option("NumberOfThreads", new_num_threads);
257+
map.add("StubBackend", backend_options.view());
258+
259+
auto status = set_option(map.entries());
260+
ASSERT_EQ(status, Error::Ok);
261+
262+
// Verify the map contains the expected data
263+
ASSERT_EQ(map.size(), 1);
264+
auto options = map.get("StubBackend");
265+
ASSERT_EQ(options.size(), 1);
266+
267+
// Verify that the backend actually received the options
268+
ASSERT_EQ(stub_backend->last_options_size, 1);
269+
ASSERT_EQ(stub_backend->last_num_threads, new_num_threads);
270+
}
271+
272+
// Test get_option functionality
273+
TEST_F(BackendUpdateTest, TestGetOption) {
274+
// First, set some options in the backend
275+
BackendOptionsMap<3> set_map;
276+
BackendOptions<1> set_backend_options;
277+
int expected_num_threads = 8;
278+
set_backend_options.set_option("NumberOfThreads", expected_num_threads);
279+
set_map.add("StubBackend", set_backend_options.view());
280+
281+
auto set_status = set_option(set_map.entries());
282+
ASSERT_EQ(set_status, Error::Ok);
283+
ASSERT_EQ(stub_backend->last_num_threads, expected_num_threads);
284+
285+
// Reset get_option tracking variables
286+
stub_backend->get_option_called = false;
287+
stub_backend->get_option_call_count = 0;
288+
stub_backend->found_expected_key = false;
289+
290+
// Now create a map with options for get_option to process
291+
BackendOptionsMap<3> get_map;
292+
BackendOptions<1> get_backend_options;
293+
get_backend_options.set_option("NumberOfThreads", 0);
294+
get_map.add("StubBackend", get_backend_options.view());
295+
296+
// Call get_option to test the API
297+
auto get_status = get_option(get_map.entries());
298+
ASSERT_EQ(get_status, Error::Ok);
299+
300+
ASSERT_TRUE(
301+
std::get<int>(get_map.entries()[0].options[0].value) ==
302+
expected_num_threads);
303+
304+
// Verify that the backend's get_option method was called correctly
305+
ASSERT_TRUE(stub_backend->get_option_called);
306+
ASSERT_EQ(stub_backend->get_option_call_count, 1);
307+
ASSERT_EQ(stub_backend->last_get_option_size, 1);
308+
ASSERT_TRUE(stub_backend->found_expected_key);
309+
}
147310
} // namespace runtime
148311
} // namespace executorch

0 commit comments

Comments
 (0)