Skip to content

Commit 1061b01

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 7aab0eb commit 1061b01

File tree

3 files changed

+268
-0
lines changed

3 files changed

+268
-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
@@ -67,5 +67,6 @@ def define_common_targets():
6767
exported_deps = [
6868
"//executorch/runtime/core:core",
6969
":options" + aten_suffix,
70+
":interface" + aten_suffix,
7071
],
7172
)

runtime/backend/test/backend_options_map_test.cpp

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <executorch/runtime/backend/options.h>
1010
#include <executorch/runtime/backend/options_map.h>
11+
#include <executorch/runtime/backend/interface.h>
1112
#include <executorch/runtime/platform/runtime.h>
1213

1314
#include <gtest/gtest.h>
@@ -21,6 +22,18 @@ using executorch::runtime::Error;
2122
using executorch::runtime::IntKey;
2223
using executorch::runtime::OptionKey;
2324
using executorch::runtime::StrKey;
25+
using executorch::runtime::BackendInterface;
26+
using executorch::runtime::BackendInitContext;
27+
using executorch::runtime::BackendExecutionContext;
28+
using executorch::runtime::BackendOptionContext;
29+
using executorch::runtime::DelegateHandle;
30+
using executorch::runtime::CompileSpec;
31+
using executorch::runtime::Backend;
32+
using executorch::runtime::FreeableBuffer;
33+
using executorch::runtime::EValue;
34+
using executorch::runtime::ArrayRef;
35+
using executorch::runtime::Result;
36+
using executorch::runtime::register_backend;
2437

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

0 commit comments

Comments
 (0)