Skip to content

Commit 0abd5bc

Browse files
committed
Backend data separation test
Pull Request resolved: #10531 Add backend data separation test with demo ExecutorBackend. Note: ExecutorBackend is a wrapper around a portable PTE file. It doesn't support delegated program-data separation in the sense of tagging tensors as external and placing them into the named data store as such. Rather, this test: 1. Creates a linear PTE file with data removed, using the portable flow. 2. Packages (1) into preprocessed blob as a delegate, using ExecutorBackend. Note: this discards the PTD portion. 3. Re-create the PTD portion via export_program (portable flow again). 4. Runs the delegated ExecutorBackend linear with portable linear.ptd file. Caveat: this means that LinearModule definition in export_program and export_delegated_program must stay in sync, as we get the PTE artifact from export_delegated_program (wrapped in ExecutorBackend), and the PTD artifact from export_program. ghstack-source-id: 282132799 @exported-using-ghexport Differential Revision: [D73679733](https://our.internmc.facebook.com/intern/diff/D73679733/)
1 parent 6b7f5e1 commit 0abd5bc

10 files changed

+187
-6
lines changed

exir/backend/test/demos/rpc/ExecutorBackend.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <executorch/runtime/core/error.h>
1919
#include <executorch/runtime/core/evalue.h>
2020
#include <executorch/runtime/core/exec_aten/util/tensor_util.h>
21+
#include <executorch/runtime/core/named_data_map.h>
2122
#include <executorch/runtime/executor/method.h>
2223
#include <executorch/runtime/executor/program.h>
2324

@@ -37,6 +38,7 @@ using ::executorch::runtime::MemoryAllocator;
3738
using ::executorch::runtime::MemoryManager;
3839
using ::executorch::runtime::Method;
3940
using ::executorch::runtime::MethodMeta;
41+
using ::executorch::runtime::NamedDataMap;
4042
using ::executorch::runtime::Program;
4143
using ::executorch::runtime::Result;
4244
using ::executorch::runtime::Span;
@@ -156,9 +158,13 @@ class ExecutorBackend final : public ::executorch::runtime::BackendInterface {
156158
new (client_memory_manager)
157159
MemoryManager(client_method_allocator, client_planned_memory);
158160

161+
const NamedDataMap* named_data_map = context.get_named_data_map();
159162
// Construct the client Method
160-
Result<Method> method_res =
161-
client_program->load_method("forward", client_memory_manager);
163+
Result<Method> method_res = client_program->load_method(
164+
"forward",
165+
client_memory_manager,
166+
/*event_tracer=*/nullptr,
167+
named_data_map);
162168
if (!method_res.ok()) {
163169
ET_LOG(
164170
Error,

exir/backend/test/demos/rpc/TARGETS

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ runtime.python_library(
1111
],
1212
visibility = [
1313
"//executorch/exir/backend/test/...",
14+
"//executorch/test/...",
1415
],
1516
deps = [
1617
"//caffe2:torch",

exir/backend/test/demos/rpc/executor_backend_preprocess.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from typing import final, List
1010

11+
from executorch.exir import ExecutorchBackendConfig
12+
1113
from executorch.exir.backend.backend_details import (
1214
BackendDetails,
1315
ExportedProgram,
@@ -24,10 +26,14 @@ def preprocess(
2426
edge_program: ExportedProgram,
2527
compile_specs: List[CompileSpec],
2628
) -> PreprocessResult:
29+
config = ExecutorchBackendConfig()
30+
for spec in compile_specs:
31+
if spec.key == "external_constants":
32+
config.external_constants = True
2733
return PreprocessResult(
2834
processed_bytes=EdgeProgramManager(
2935
edge_programs=edge_program,
3036
)
31-
.to_executorch()
37+
.to_executorch(config)
3238
.buffer,
3339
)

exir/backend/test/demos/rpc/targets.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def define_common_targets():
4040
],
4141
visibility = [
4242
"//executorch/exir/backend/test/...",
43+
"//executorch/runtime/executor/test/...",
4344
],
4445
deps = [
4546
":executor_backend",

runtime/executor/method.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ Result<size_t> Method::get_num_external_constants() {
329329
}
330330

331331
Error Method::parse_external_constants(const NamedDataMap* named_data_map) {
332+
ET_CHECK_OR_RETURN_ERROR(named_data_map != nullptr, InvalidState, "named_data_map is null");
332333
auto flatbuffer_values = serialization_plan_->values();
333334
size_t n_value = flatbuffer_values->size();
334335

@@ -372,6 +373,7 @@ Error Method::parse_external_constants(const NamedDataMap* named_data_map) {
372373
Result<const TensorLayout> tensor_layout =
373374
named_data_map->get_metadata(key);
374375
if (!tensor_layout.ok()) {
376+
ET_LOG(Info, "Failed to get metadata for key %s", key);
375377
return tensor_layout.error();
376378
}
377379
// Check external tensor compatibility.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
2+
/*
3+
* Copyright (c) Meta Platforms, Inc. and affiliates.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the BSD-style license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
#include <executorch/exir/backend/test/demos/rpc/ExecutorBackend.h>
11+
#include <executorch/extension/data_loader/file_data_loader.h>
12+
#include <executorch/extension/flat_tensor/flat_tensor_data_map.h>
13+
#include <executorch/runtime/core/error.h>
14+
#include <executorch/runtime/core/result.h>
15+
#include <executorch/runtime/executor/method.h>
16+
#include <executorch/runtime/executor/program.h>
17+
#include <executorch/runtime/executor/test/managed_memory_manager.h>
18+
#include <executorch/runtime/platform/runtime.h>
19+
20+
#include <gtest/gtest.h>
21+
22+
using namespace ::testing;
23+
using executorch::extension::FlatTensorDataMap;
24+
using executorch::runtime::DataLoader;
25+
using executorch::runtime::Error;
26+
using executorch::runtime::Method;
27+
using executorch::runtime::Program;
28+
using executorch::runtime::Result;
29+
using executorch::runtime::testing::ManagedMemoryManager;
30+
using torch::executor::util::FileDataLoader;
31+
32+
constexpr size_t kDefaultNonConstMemBytes = 32 * 1024U;
33+
constexpr size_t kDefaultRuntimeMemBytes = 32 * 1024U;
34+
35+
class BackendDataSeparationTest : public ::testing::Test {
36+
protected:
37+
void SetUp() override {
38+
// Since these tests cause ET_LOG to be called, the PAL must be initialized
39+
// first.
40+
executorch::runtime::runtime_init();
41+
42+
// Make sure that the backend has been registered. Safe to call multiple
43+
// times. Doing this at runtime ensures that it's only registered if these
44+
// tests are run.
45+
ASSERT_EQ(example::register_executor_backend(), Error::Ok);
46+
47+
// Create data loaders.
48+
Result<FileDataLoader> linear_program_loader = FileDataLoader::from(
49+
std::getenv("ET_MODULE_LINEAR_DELEGATE_PROGRAM_PATH"));
50+
ASSERT_EQ(linear_program_loader.error(), Error::Ok);
51+
linear_program_loader_ = std::make_unique<FileDataLoader>(
52+
std::move(linear_program_loader.get()));
53+
54+
Result<FileDataLoader> linear_data_loader =
55+
FileDataLoader::from(std::getenv("ET_MODULE_LINEAR_DATA_PATH"));
56+
ASSERT_EQ(linear_data_loader.error(), Error::Ok);
57+
linear_data_loader_ =
58+
std::make_unique<FileDataLoader>(std::move(linear_data_loader.get()));
59+
60+
// Create programs.
61+
Result<Program> linear_program = Program::load(
62+
linear_program_loader_.get(),
63+
Program::Verification::InternalConsistency);
64+
ASSERT_EQ(linear_program.error(), Error::Ok);
65+
linear_program_ =
66+
std::make_unique<Program>(std::move(linear_program.get()));
67+
68+
Result<FlatTensorDataMap> linear_data_map =
69+
FlatTensorDataMap::load(linear_data_loader_.get());
70+
EXPECT_EQ(linear_data_map.error(), Error::Ok);
71+
linear_data_map_ =
72+
std::make_unique<FlatTensorDataMap>(std::move(linear_data_map.get()));
73+
74+
ET_LOG(Info, "setup done, named_data_map_ = %lu", linear_data_map_->get_num_keys().get());
75+
}
76+
77+
private:
78+
std::unique_ptr<FileDataLoader> linear_program_loader_;
79+
std::unique_ptr<FileDataLoader> linear_data_loader_;
80+
81+
protected:
82+
std::unique_ptr<Program> linear_program_;
83+
std::unique_ptr<FlatTensorDataMap> linear_data_map_;
84+
};
85+
86+
TEST_F(BackendDataSeparationTest, TestSeparation) {
87+
ManagedMemoryManager mmm(kDefaultNonConstMemBytes, kDefaultRuntimeMemBytes);
88+
Result<Method> method = linear_program_->load_method(
89+
"forward", &mmm.get(), /*event_tracer=*/nullptr, /*named_data_map=*/linear_data_map_.get());
90+
ASSERT_EQ(method.error(), Error::Ok);
91+
92+
// Can execute the method.
93+
Error err = method->execute();
94+
ASSERT_EQ(err, Error::Ok);
95+
}

runtime/executor/test/targets.bzl

+22
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,28 @@ def define_common_targets(is_fbcode = False):
240240
},
241241
)
242242

243+
runtime.cxx_test(
244+
name = "backend_data_separation_test",
245+
srcs = [
246+
"backend_data_separation_test.cpp",
247+
],
248+
deps = [
249+
":managed_memory_manager",
250+
"//executorch/runtime/executor:program",
251+
"//executorch/extension/data_loader:file_data_loader",
252+
"//executorch/exir/backend/test/demos/rpc:executor_backend",
253+
"//executorch/exir/backend/test/demos/rpc:executor_backend_register",
254+
"//executorch/extension/flat_tensor:flat_tensor_data_map",
255+
],
256+
env = {
257+
# The tests use these vars to find the program files to load.
258+
# Uses an fbcode target path because the authoring/export tools
259+
# intentionally don't work in xplat (since they're host-only
260+
# tools).
261+
"ET_MODULE_LINEAR_DELEGATE_PROGRAM_PATH": "$(location fbcode//executorch/test/models:exported_executor_backend_program_and_data[ModuleLinear-e.pte])",
262+
"ET_MODULE_LINEAR_DATA_PATH": "$(location fbcode//executorch/test/models:exported_program_and_data[ModuleLinear.ptd])",
263+
},
264+
)
243265
runtime.cxx_test(
244266
name = "memory_manager_test",
245267
srcs = [

test/models/export_delegated_program.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@
2020
from executorch.exir import EdgeCompileConfig, to_edge, to_edge_transform_and_lower
2121
from executorch.exir.backend.backend_api import to_backend
2222
from executorch.exir.backend.backend_details import BackendDetails, PreprocessResult
23+
from executorch.exir.backend.compile_spec_schema import CompileSpec
2324
from executorch.exir.backend.test.backend_with_compiler_demo import (
2425
BackendWithCompilerDemo,
2526
)
2627
from executorch.exir.passes.external_constants_pass import (
2728
delegate_external_constants_pass,
29+
)
30+
from executorch.exir.backend.test.demos.rpc.executor_backend_preprocess import (
31+
ExecutorBackend,
2832
)
2933
from executorch.exir.program import ExecutorchProgramManager
3034
from torch import nn
@@ -150,13 +154,18 @@ def __init__(self, fn, method_name=method_name):
150154
def forward(self, *args, **kwargs):
151155
return getattr(self.fn, self.method_name)(*args, **kwargs)
152156

153-
exported_program = export(WrapperModule(eager_module), args=inputs, strict=True)
157+
if method_name != "forward":
158+
# Only require wrapper module if we're exporting a specific method other than forward.
159+
exported_program = export(WrapperModule(eager_module), args=inputs, strict=True)
160+
else:
161+
exported_program = export(eager_module, args=inputs, strict=True)
154162

155163
edge_config = EdgeCompileConfig(_check_ir_validity=False)
156164
et_config = exir.ExecutorchBackendConfig(
157165
extract_delegate_segments=extract_delegate_segments,
158166
constant_tensor_alignment=constant_tensor_alignment,
159167
delegate_alignment=delegate_alignment,
168+
external_constants=external_constants,
160169
)
161170

162171
if backend_id == "XnnpackBackend":
@@ -181,7 +190,10 @@ def forward(self, *args, **kwargs):
181190
else:
182191
edge: exir.EdgeProgramManager = to_edge(exported_program)
183192
lowered_module = to_backend( # type: ignore[call-arg]
184-
backend_id, edge.exported_program(), compile_specs=[]
193+
backend_id,
194+
edge.exported_program(),
195+
# Just for the demo executor_backend.
196+
compile_specs=[CompileSpec(key="external_constants", value=b"")],
185197
)
186198

187199
class CompositeModule(nn.Module):

test/models/export_program.py

+13
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,19 @@ def get_random_inputs(self):
146146
return (torch.ones(2, 2, dtype=torch.float),)
147147

148148

149+
# Used for program-data-separation.
150+
class ModuleLinear(torch.nn.Module):
151+
def __init__(self):
152+
super().__init__()
153+
self.linear = torch.nn.Linear(3, 3)
154+
155+
def forward(self, x: torch.Tensor):
156+
return self.linear(x)
157+
158+
def get_random_inputs(self):
159+
return (torch.randn(3),)
160+
161+
149162
class ModuleMultipleEntry(torch.nn.Module):
150163
def __init__(self):
151164
super().__init__()

test/models/targets.bzl

+24-1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def define_common_targets():
9595
# Class names of nn.Modules for :exported_programs to export.
9696
MODULES_AND_DATA_TO_EXPORT = [
9797
"ModuleAddMul",
98+
"ModuleLinear",
9899
"ModuleSimpleTrain",
99100
]
100101

@@ -104,6 +105,8 @@ def define_common_targets():
104105
outs = {
105106
"ModuleAddMul.pte": ["ModuleAddMulProgram.pte"],
106107
"ModuleAddMul.ptd": ["ModuleAddMulProgram.ptd"],
108+
"ModuleLinear.pte": ["ModuleLinearProgram.pte"],
109+
"ModuleLinear.ptd": ["ModuleLinearProgram.ptd"],
107110
"ModuleSimpleTrainProgram.pte": ["ModuleSimpleTrainProgram.pte"],
108111
"ModuleSimpleTrain.ptd": ["ModuleSimpleTrainProgram.ptd"],
109112
},
@@ -146,7 +149,7 @@ def define_common_targets():
146149
deps = [
147150
":export_delegated_program_lib",
148151
"//executorch/backends/xnnpack/partition:xnnpack_partitioner",
149-
152+
"//executorch/exir/backend/test/demos/rpc:executor_backend_preprocess",
150153
],
151154
visibility = [], # Private
152155
)
@@ -225,3 +228,23 @@ def define_common_targets():
225228
"//executorch/test/...",
226229
],
227230
)
231+
232+
# Export with demo ExecutorBackend for program-data separation test.
233+
runtime.genrule(
234+
name = "exported_executor_backend_program_and_data",
235+
cmd = "$(exe :export_delegated_program)" +
236+
" --modules ModuleLinear" +
237+
" --backend_id ExecutorBackend" +
238+
" --external_constants" +
239+
" --outdir $OUT",
240+
241+
outs = {
242+
"ModuleLinear-e.pte": ["ModuleLinear-e.pte"],
243+
},
244+
default_outs = ["."],
245+
visibility = [
246+
"//executorch/runtime/executor/test/...",
247+
"//executorch/extension/flat_tensor/test/...",
248+
"//executorch/test/...",
249+
],
250+
)

0 commit comments

Comments
 (0)