Skip to content

Commit 97617ff

Browse files
Gasoonjiafacebook-github-bot
authored andcommitted
Extend PyBundledModule with extension.BundledModule (#12839)
Summary: Pull Request resolved: #12839 Differential Revision: D78938344
1 parent 0479dcd commit 97617ff

File tree

7 files changed

+115
-125
lines changed

7 files changed

+115
-125
lines changed

CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,14 @@ if(EXECUTORCH_BUILD_PYBIND)
707707
torch
708708
)
709709

710+
if(EXECUTORCH_BUILD_EXTENSION_MODULE)
711+
if(CMAKE_TOOLCHAIN_IOS OR CMAKE_TOOLCHAIN_ANDROID OR APPLE)
712+
list(APPEND _dep_libs extension_module_static)
713+
else()
714+
list(APPEND _dep_libs extension_module)
715+
endif()
716+
endif()
717+
710718
if(EXECUTORCH_BUILD_TESTS)
711719
list(APPEND _dep_libs test_backend_compiler_lib)
712720
endif()
@@ -764,6 +772,17 @@ if(EXECUTORCH_BUILD_PYBIND)
764772
target_compile_options(portable_lib PUBLIC ${_pybind_compile_options})
765773
target_link_libraries(portable_lib PRIVATE ${_dep_libs})
766774

775+
# Set RPATH for portable_lib to find libextension_module.so
776+
if(APPLE)
777+
set_target_properties(portable_lib PROPERTIES
778+
BUILD_RPATH "@loader_path"
779+
INSTALL_RPATH "@loader_path")
780+
else()
781+
set_target_properties(portable_lib PROPERTIES
782+
BUILD_RPATH "$ORIGIN"
783+
INSTALL_RPATH "$ORIGIN")
784+
endif()
785+
767786
install(
768787
TARGETS portable_lib
769788
EXPORT ExecuTorchTargets

devtools/bundled_program/test/test_end2end.py

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,7 @@
55
# LICENSE file in the root directory of this source tree.
66

77
# flake8: noqa: F401
8-
import functools
9-
import inspect
10-
import os
11-
import random
128
import unittest
13-
from typing import Callable, Dict, Optional, Tuple, Type
14-
15-
import executorch.exir as exir
16-
17-
import executorch.exir.control_flow as control_flow
18-
19-
# @manual=//executorch/extension/pytree:pybindings
20-
import executorch.extension.pytree as pytree
21-
22-
import torch
239

2410
from executorch.devtools.bundled_program.core import BundledProgram
2511
from executorch.devtools.bundled_program.serialize import (
@@ -35,8 +21,6 @@
3521
try:
3622
from executorch.extension.pybindings.portable_lib import (
3723
_load_bundled_program_from_buffer,
38-
_load_for_executorch_from_buffer,
39-
_load_for_executorch_from_bundled_program,
4024
)
4125

4226
kernel_mode = "lean"
@@ -47,8 +31,6 @@
4731
try:
4832
from executorch.extension.pybindings.aten_lib import ( # @manual=//executorch/extension/pybindings:aten_lib
4933
_load_bundled_program_from_buffer,
50-
_load_for_executorch_from_buffer,
51-
_load_for_executorch_from_bundled_program,
5234
)
5335

5436
assert kernel_mode is None
@@ -75,19 +57,8 @@ def test_sample_model_e2e(self):
7557
bundled_program_buffer
7658
)
7759

78-
executorch_module = _load_for_executorch_from_bundled_program(
79-
executorch_bundled_program
80-
)
81-
8260
for method_name in eager_model.method_names:
83-
executorch_module.load_bundled_input(
84-
executorch_bundled_program,
85-
method_name,
86-
0,
87-
)
88-
executorch_module.plan_execute(method_name)
89-
executorch_module.verify_result_with_bundled_expected_output(
90-
executorch_bundled_program,
61+
executorch_bundled_program.verify_result_with_bundled_expected_output(
9162
method_name,
9263
0,
9364
)

extension/pybindings/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ CMAKE_ARGS="-DEXECUTORCH_BUILD_MPS=ON" ./install_executorch.sh
2727
- `_reset_profile_results()`: Reset profile results.
2828
## Classes
2929
### ExecuTorchModule
30-
- `load_bundled_input()`: Load bundled input.
31-
- `verify_result_with_bundled_expected_output(bundle: str, method_name: str, testset_idx: int, rtol: float = 1e-5, atol: float = 1e-8)`: Verify result with bundled expected output.
3230
- `plan_execute()`: Plan and execute.
3331
- `run_method()`: Run method.
3432
- `forward()`: Forward. This takes a pytree-flattend PyTorch-tensor-based input.
@@ -37,5 +35,6 @@ CMAKE_ARGS="-DEXECUTORCH_BUILD_MPS=ON" ./install_executorch.sh
3735
- `__call__()`: Call method.
3836
### BundledModule
3937
This class is currently empty and serves as a placeholder for future methods and attributes.
38+
- `verify_result_with_bundled_expected_output(method_name: str, testset_idx: int, rtol: float = 1e-5, atol: float = 1e-8)`: Verify result with bundled expected output.
4039
## Note
4140
All functions and methods are guarded by a call guard that redirects `cout` and `cerr` to the Python environment.

extension/pybindings/pybindings.cpp

Lines changed: 82 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <executorch/extension/data_loader/buffer_data_loader.h>
2424
#include <executorch/extension/data_loader/mmap_data_loader.h>
2525
#include <executorch/extension/memory_allocator/malloc_memory_allocator.h>
26+
#include <executorch/extension/module/bundled_module.h>
2627
#include <executorch/extension/threadpool/threadpool.h>
2728
#include <executorch/runtime/backend/interface.h>
2829
#include <executorch/runtime/core/data_loader.h>
@@ -81,6 +82,7 @@ using ::executorch::ET_RUNTIME_NAMESPACE::Program;
8182
using ::executorch::extension::BufferDataLoader;
8283
using ::executorch::extension::MallocMemoryAllocator;
8384
using ::executorch::extension::MmapDataLoader;
85+
using ::executorch::extension::ET_BUNDLED_MODULE_NAMESPACE::BundledModule;
8486
using ::executorch::runtime::ArrayRef;
8587
using ::executorch::runtime::DataLoader;
8688
using ::executorch::runtime::Error;
@@ -425,13 +427,54 @@ inline std::unique_ptr<Module> load_module_from_file(
425427
program_verification);
426428
}
427429

430+
inline py::list get_outputs_as_py_list(
431+
const std::vector<EValue>& outputs,
432+
bool clone_outputs = true) {
433+
const auto outputs_size = outputs.size();
434+
py::list list(outputs_size);
435+
for (size_t i = 0; i < outputs_size; ++i) {
436+
auto& v = outputs[i];
437+
if (Tag::None == v.tag) {
438+
list[i] = py::none();
439+
} else if (Tag::Int == v.tag) {
440+
list[i] = py::cast(v.toInt());
441+
} else if (Tag::Double == v.tag) {
442+
list[i] = py::cast(v.toDouble());
443+
} else if (Tag::Bool == v.tag) {
444+
list[i] = py::cast(v.toBool());
445+
} else if (Tag::String == v.tag) {
446+
list[i] = py::cast(std::string(v.toString().data()));
447+
} else if (Tag::Tensor == v.tag) {
448+
#ifdef USE_ATEN_LIB
449+
// Clone so the outputs in python do not share a lifetime with the
450+
// module object
451+
if (clone_outputs) {
452+
list[i] = py::cast(v.toTensor().clone());
453+
} else {
454+
list[i] = py::cast(v.toTensor());
455+
}
456+
#else
457+
if (clone_outputs) {
458+
list[i] = py::cast(alias_attensor_to_etensor(v.toTensor()).clone());
459+
} else {
460+
list[i] = py::cast(alias_attensor_to_etensor(v.toTensor()));
461+
}
462+
#endif
463+
} else {
464+
ET_ASSERT_UNREACHABLE_MSG("Invalid model output type");
465+
}
466+
}
467+
return list;
468+
}
469+
428470
static constexpr size_t kDEFAULT_BUNDLED_INPUT_POOL_SIZE = 16 * 1024U;
429471

430-
struct PyBundledModule final {
472+
struct PyBundledModule : public BundledModule {
431473
explicit PyBundledModule(
432474
const py::bytes& buffer,
433475
uint32_t bundled_input_pool_size)
434-
: bundled_program_ptr_(buffer),
476+
: BundledModule(buffer.cast<std::string_view>().data()),
477+
bundled_program_ptr_(buffer),
435478
program_ptr_(static_cast<const void*>(
436479
bundled_program_flatbuffer::GetBundledProgram(
437480
get_bundled_program_ptr())
@@ -460,6 +503,33 @@ struct PyBundledModule final {
460503
return program_len_;
461504
}
462505

506+
py::list verify_result_with_bundled_expected_output(
507+
const std::string& method_name,
508+
size_t testset_idx,
509+
double rtol = 1e-5,
510+
double atol = 1e-8) {
511+
// Execute the method
512+
auto result = BundledModule::execute(method_name, testset_idx);
513+
if (!result.ok()) {
514+
THROW_IF_ERROR(
515+
result.error(),
516+
"Method execution failed with status 0x%" PRIx32,
517+
static_cast<uint32_t>(result.error()));
518+
}
519+
520+
// Convert outputs to py::list
521+
const auto& outputs = result.get();
522+
py::list py_outputs = get_outputs_as_py_list(outputs);
523+
524+
Error status = BundledModule::verify_method_outputs(
525+
method_name, testset_idx, rtol, atol);
526+
THROW_IF_ERROR(
527+
status,
528+
"Result verification failed with status %" PRIu32,
529+
static_cast<uint32_t>(status));
530+
return py_outputs;
531+
}
532+
463533
private:
464534
// Store the bytes object instead of a raw pointer so that this module will
465535
// keep the bytes alive.
@@ -853,43 +923,6 @@ struct PyModule final {
853923
}
854924
}
855925

856-
void load_bundled_input(
857-
PyBundledModule& m,
858-
const std::string method_name,
859-
size_t testset_idx) {
860-
const void* bundled_program_ptr = m.get_bundled_program_ptr();
861-
Error status = executorch::BUNDLED_PROGRAM_NAMESPACE::load_bundled_input(
862-
module_->get_method(method_name), bundled_program_ptr, testset_idx);
863-
THROW_IF_ERROR(
864-
status,
865-
"load_bundled_input failed with status 0x%" PRIx32,
866-
static_cast<uint32_t>(status));
867-
}
868-
869-
py::list verify_result_with_bundled_expected_output(
870-
PyBundledModule& m,
871-
const std::string method_name,
872-
size_t testset_idx,
873-
double rtol = 1e-5,
874-
double atol = 1e-8) {
875-
const void* bundled_program_ptr = m.get_bundled_program_ptr();
876-
auto& method = module_->get_method(method_name);
877-
Error status = executorch::BUNDLED_PROGRAM_NAMESPACE::load_bundled_input(
878-
method, bundled_program_ptr, testset_idx);
879-
THROW_IF_ERROR(
880-
status,
881-
"load_bundled_input failed with status 0x%" PRIx32,
882-
static_cast<uint32_t>(status));
883-
py::list outputs = plan_execute(method_name);
884-
status = executorch::BUNDLED_PROGRAM_NAMESPACE::verify_method_outputs(
885-
method, bundled_program_ptr, testset_idx, rtol, atol);
886-
THROW_IF_ERROR(
887-
status,
888-
"Result verification failed with status %" PRIu32,
889-
static_cast<uint32_t>(status));
890-
return outputs;
891-
}
892-
893926
py::list plan_execute(
894927
const std::string method_name,
895928
bool clone_outputs = true) {
@@ -912,46 +945,6 @@ struct PyModule final {
912945
return get_outputs_as_py_list(outputs, clone_outputs);
913946
}
914947

915-
py::list get_outputs_as_py_list(
916-
const std::vector<EValue>& outputs,
917-
bool clone_outputs = true) {
918-
const auto outputs_size = outputs.size();
919-
py::list list(outputs_size);
920-
for (size_t i = 0; i < outputs_size; ++i) {
921-
auto& v = outputs[i];
922-
if (Tag::None == v.tag) {
923-
list[i] = py::none();
924-
} else if (Tag::Int == v.tag) {
925-
list[i] = py::cast(v.toInt());
926-
} else if (Tag::Double == v.tag) {
927-
list[i] = py::cast(v.toDouble());
928-
} else if (Tag::Bool == v.tag) {
929-
list[i] = py::cast(v.toBool());
930-
} else if (Tag::String == v.tag) {
931-
list[i] = py::cast(std::string(v.toString().data()));
932-
} else if (Tag::Tensor == v.tag) {
933-
#ifdef USE_ATEN_LIB
934-
// Clone so the outputs in python do not share a lifetime with the
935-
// module object
936-
if (clone_outputs) {
937-
list[i] = py::cast(v.toTensor().clone());
938-
} else {
939-
list[i] = py::cast(v.toTensor());
940-
}
941-
#else
942-
if (clone_outputs) {
943-
list[i] = py::cast(alias_attensor_to_etensor(v.toTensor()).clone());
944-
} else {
945-
list[i] = py::cast(alias_attensor_to_etensor(v.toTensor()));
946-
}
947-
#endif
948-
} else {
949-
ET_ASSERT_UNREACHABLE_MSG("Invalid model output type");
950-
}
951-
}
952-
return list;
953-
}
954-
955948
std::unique_ptr<PyMethodMeta> method_meta(const std::string method_name) {
956949
auto& method = module_->get_method(method_name);
957950
return std::make_unique<PyMethodMeta>(module_, method.method_meta());
@@ -1583,16 +1576,6 @@ PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
15831576
call_guard);
15841577

15851578
py::class_<PyModule>(m, "ExecuTorchModule")
1586-
.def("load_bundled_input", &PyModule::load_bundled_input, call_guard)
1587-
.def(
1588-
"verify_result_with_bundled_expected_output",
1589-
&PyModule::verify_result_with_bundled_expected_output,
1590-
py::arg("bundle"),
1591-
py::arg("method_name"),
1592-
py::arg("testset_idx"),
1593-
py::arg("rtol") = 1e-5,
1594-
py::arg("atol") = 1e-8,
1595-
call_guard)
15961579
.def(
15971580
"plan_execute",
15981581
&PyModule::plan_execute,
@@ -1638,7 +1621,16 @@ PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
16381621
py::arg("clone_outputs") = true,
16391622
call_guard);
16401623

1641-
py::class_<PyBundledModule>(m, "BundledModule");
1624+
py::class_<PyBundledModule>(m, "BundledModule")
1625+
.def(
1626+
"verify_result_with_bundled_expected_output",
1627+
&PyBundledModule::verify_result_with_bundled_expected_output,
1628+
py::arg("method_name"),
1629+
py::arg("testset_idx"),
1630+
py::arg("rtol") = 1e-5,
1631+
py::arg("atol") = 1e-8,
1632+
call_guard);
1633+
16421634
py::class_<PyTensorInfo>(m, "TensorInfo")
16431635
.def("sizes", &PyTensorInfo::sizes, call_guard)
16441636
.def("dtype", &PyTensorInfo::dtype, call_guard)

kernels/quantized/CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ if(NOT CMAKE_GENERATOR STREQUAL "Xcode"
115115

116116
# pip wheels will need to be able to find the dependent libraries. On
117117
# Linux, the .so has non-absolute dependencies on libs like
118-
# "_portable_lib.so" without paths; as long as we `import torch` first,
118+
# "_portable_lib.so" and "libextension_module.so" without paths; as long as we `import torch` first,
119119
# those dependencies will work. But Apple dylibs do not support
120120
# non-absolute dependencies, so we need to tell the loader where to look
121121
# for its libraries. The LC_LOAD_DYLIB entries for the portable_lib
@@ -124,9 +124,9 @@ if(NOT CMAKE_GENERATOR STREQUAL "Xcode"
124124
# installed location of our _portable_lib.so file. To see these LC_*
125125
# values, run `otool -l libquantized_ops_lib.dylib`.
126126
if(APPLE)
127-
set(RPATH "@loader_path/../../extensions/pybindings")
127+
set(RPATH "@loader_path/../../extension/pybindings")
128128
else()
129-
set(RPATH "$ORIGIN/../../extensions/pybindings")
129+
set(RPATH "$ORIGIN/../../extension/pybindings")
130130
endif()
131131
set_target_properties(
132132
quantized_ops_aot_lib PROPERTIES BUILD_RPATH ${RPATH} INSTALL_RPATH

shim_et/xplat/executorch/extension/pybindings/pybindings.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ PORTABLE_MODULE_DEPS = [
1616
"//executorch/extension/data_loader:buffer_data_loader",
1717
"//executorch/extension/data_loader:mmap_data_loader",
1818
"//executorch/extension/memory_allocator:malloc_memory_allocator",
19+
"//executorch/extension/module:module",
20+
"//executorch/extension/module:bundled_module",
1921
"//executorch/runtime/executor/test:test_backend_compiler_lib",
2022
"//executorch/devtools/etdump:etdump_flatcc",
2123
] + get_all_cpu_backend_targets()
@@ -28,6 +30,8 @@ ATEN_MODULE_DEPS = [
2830
"//executorch/extension/data_loader:buffer_data_loader",
2931
"//executorch/extension/data_loader:mmap_data_loader",
3032
"//executorch/extension/memory_allocator:malloc_memory_allocator",
33+
"//executorch/extension/module:module_aten",
34+
"//executorch/extension/module:bundled_module_aten",
3135
"//executorch/devtools/bundled_program:runtime_aten",
3236
"//executorch/runtime/executor/test:test_backend_compiler_lib_aten",
3337
"//executorch/devtools/etdump:etdump_flatcc",

tools/cmake/preset/default.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ check_required_options_on(
272272
EXECUTORCH_BUILD_EXTENSION_DATA_LOADER EXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR
273273
)
274274

275+
check_required_options_on(
276+
IF_ON EXECUTORCH_BUILD_PYBIND REQUIRES
277+
EXECUTORCH_BUILD_EXTENSION_MODULE
278+
)
279+
275280
check_required_options_on(
276281
IF_ON EXECUTORCH_BUILD_KERNELS_LLM REQUIRES
277282
EXECUTORCH_BUILD_KERNELS_OPTIMIZED

0 commit comments

Comments
 (0)