Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 43 additions & 14 deletions extension/pybindings/pybindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,22 +174,42 @@ inline std::unique_ptr<Module> load_module_from_buffer(
}

inline std::unique_ptr<Module> load_module_from_file(
const std::string& path,
const std::string& program_path,
std::optional<const std::string>& data_map_path,
std::unique_ptr<runtime::EventTracer> event_tracer,
Program::Verification program_verification) {
EXECUTORCH_SCOPE_PROF("load_module_from_file");

Result<MmapDataLoader> res = MmapDataLoader::from(
path.c_str(), MmapDataLoader::MlockConfig::UseMlockIgnoreErrors);
Result<MmapDataLoader> program_loader_res = MmapDataLoader::from(
program_path.c_str(), MmapDataLoader::MlockConfig::UseMlockIgnoreErrors);
THROW_IF_ERROR(
res.error(),
program_loader_res.error(),
"Failed to create MmapDataLoader from file %s, error: 0x:%" PRIx32,
path.c_str(),
static_cast<uint32_t>(res.error()));

auto loader = std::make_unique<MmapDataLoader>(std::move(res.get()));
program_path.c_str(),
static_cast<uint32_t>(program_loader_res.error()));
auto program_loader =
std::make_unique<MmapDataLoader>(std::move(program_loader_res.get()));

if (data_map_path.has_value()) {
Result<MmapDataLoader> data_map_loader_res = MmapDataLoader::from(
data_map_path->c_str(),
MmapDataLoader::MlockConfig::UseMlockIgnoreErrors);
THROW_IF_ERROR(
data_map_loader_res.error(),
"Failed to create MmapDataLoader from file %s, error: 0x:%" PRIx32,
data_map_path->c_str(),
static_cast<uint32_t>(data_map_loader_res.error()));
auto data_map_loader =
std::make_unique<MmapDataLoader>(std::move(data_map_loader_res.get()));
return std::make_unique<Module>(
std::move(program_loader),
nullptr, // memory_allocator
nullptr, // temp_allocator
std::move(event_tracer), // event_tracer
std::move(data_map_loader)); // data_map_loader
}
return std::make_unique<Module>(
std::move(loader),
std::move(program_loader),
nullptr, // memory_allocator
nullptr, // temp_allocator
std::move(event_tracer), // event_tracer
Expand Down Expand Up @@ -510,14 +530,16 @@ struct PyModule final {
program_verification)) {}

explicit PyModule(
const std::string& path,
const std::string& program_path,
std::optional<const std::string>& data_path,
bool enable_etdump,
size_t debug_buffer_size = 0,
Program::Verification program_verification =
Program::Verification::InternalConsistency)
: debug_buffer_size_(debug_buffer_size),
module_(load_module_from_file(
path,
program_path,
data_path,
setup_event_tracer(enable_etdump, debug_buffer_size),
program_verification)) {}

Expand All @@ -536,14 +558,20 @@ struct PyModule final {
return std::make_unique<PyModule>(
buffer, enable_etdump, debug_buffer_size, program_verification);
}

static std::unique_ptr<PyModule> load_from_file(
const std::string& path,
const std::string& program_path,
std::optional<const std::string>& data_path,
bool enable_etdump,
size_t debug_buffer_size = 0,
Program::Verification program_verification =
Program::Verification::InternalConsistency) {
return std::make_unique<PyModule>(
path, enable_etdump, debug_buffer_size, program_verification);
program_path,
data_path,
enable_etdump,
debug_buffer_size,
program_verification);
}

static std::unique_ptr<PyModule> load_from_bundled_program(
Expand Down Expand Up @@ -1351,7 +1379,8 @@ PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
m.def(
"_load_for_executorch",
PyModule::load_from_file,
py::arg("path"),
py::arg("program_path"),
py::arg("data_path") = std::nullopt,
py::arg("enable_etdump") = false,
py::arg("debug_buffer_size") = 0,
py::arg("program_verification") =
Expand Down
6 changes: 4 additions & 2 deletions extension/pybindings/pybindings.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ class MethodMeta:

@experimental("This API is experimental and subject to change without notice.")
def _load_for_executorch(
path: str,
program_path: str,
data_path: Optional[str] = None,
enable_etdump: bool = False,
debug_buffer_size: int = 0,
program_verification: Verification = Verification.InternalConsistency,
Expand All @@ -168,7 +169,8 @@ def _load_for_executorch(
This API is experimental and subject to change without notice.

Args:
path: File path to the ExecuTorch program as a string.
program_path: File path to the ExecuTorch program as a string.
data_path: File path to a .ptd file containing data used by the program.
enable_etdump: If true, enables an ETDump which can store profiling information.
See documentation at https://pytorch.org/executorch/main/etdump
for how to use it.
Expand Down
15 changes: 15 additions & 0 deletions extension/pybindings/test/make_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,21 @@ def get_inputs(self):
return (torch.ones(2, 2), torch.ones(2, 2))


class ModuleLinear(torch.nn.Module):
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(3, 3)

def forward(self, x: torch.Tensor):
return self.linear(x)

def get_methods_to_export(self):
return ("forward",)

def get_inputs(self):
return (torch.randn(3),)


def create_program(
eager_module: torch.nn.Module,
et_config: Optional[ExecutorchBackendConfig] = None,
Expand Down
33 changes: 33 additions & 0 deletions extension/pybindings/test/test_pybindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ModuleAddWithAttributes,
ModuleChannelsLast,
ModuleChannelsLastInDefaultOut,
ModuleLinear,
ModuleMulti,
)
from torch.export import export
Expand Down Expand Up @@ -623,3 +624,35 @@ def test_method_method_meta(self) -> None:
self.assertEqual(output_tensor.is_memory_planned(), True)
self.assertEqual(output_tensor.nbytes(), 16)
self.assertEqual(str(output_tensor), tensor_info)

def test_program_data_separation(self) -> None:
eager_module = ModuleLinear()
inputs = eager_module.get_inputs()
exported_program = export(eager_module, inputs, strict=True)
exec_program = to_edge(exported_program).to_executorch(
config=ExecutorchBackendConfig(
# Move all tensor data to '_default_external_constant' file.
external_constants=True,
)
)

import os
import tempfile

with tempfile.TemporaryDirectory() as tmpdir:
pte_file = os.path.join(tmpdir, "linear.pte")
with open(pte_file, "wb") as f:
f.write(exec_program.buffer)

ptd_file = os.path.join(tmpdir, "linear.ptd")
with open(ptd_file, "wb") as ptd:
tensor_data = bytes(
exec_program._tensor_data.pop("_default_external_constant")
)
ptd.write(tensor_data)

executorch_program = self.runtime._load_for_executorch(pte_file, ptd_file)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably need to update the pyi

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine to update the legacy API (methods that start with _) but we have modern pybindings APIs that we want users to use.

https://colab.research.google.com/drive/1qpxrXC3YdJQzly3mRg-4ayYiOjC6rue3?usp=sharing#scrollTo=Kp-MxVVHkirE

Don't we need to update runtime.load_program(pte, ptd) too?

Copy link
Contributor Author

@lucylq lucylq Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Mergen, sorry for the late response. I think runtime.load_program is only intended to take a PTE file --> it understands and deserializes the PTE file format (not PTD). The C++ Program object doesn't hold a PTD file.

We can potentially support this in load_method, which takes a NamedDataMap (from PTD) as an optional argument. We'd need to add pybindings on extension/flat_tensor to load the PTD file for this.

Having it in Module may be OK at the moment, let me know what you think (I can add pybindings for extension/flat_tensor in a separate PR).


expected = eager_module(inputs[0])
executorch_output = executorch_program.forward(inputs)[0]
self.assertTrue(torch.allclose(expected, executorch_output))
Loading