Skip to content

Commit 75f339b

Browse files
Gasoonjiafacebook-github-bot
authored andcommitted
support etdump generation in executorch.runtime (#13464)
Summary: make et.runtime support etdump generation. Differential Revision: D80373976
1 parent b02db12 commit 75f339b

File tree

5 files changed

+167
-7
lines changed

5 files changed

+167
-7
lines changed

devtools/etdump/TARGETS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ runtime.python_library(
2929
},
3030
visibility = [
3131
"//executorch/devtools/...",
32+
"//executorch/runtime/test/...",
3233
],
3334
deps = [
3435
"fbsource//third-party/pypi/setuptools:setuptools",

extension/pybindings/pybindings.cpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1449,7 +1449,7 @@ struct PyProgram final {
14491449

14501450
std::unique_ptr<PyMethod> load_method(const std::string& method_name) {
14511451
Result<Method> res = state_->program_->load_method(
1452-
method_name.c_str(), memory_->mem_manager());
1452+
method_name.c_str(), memory_->mem_manager(), event_tracer_.get());
14531453
THROW_IF_ERROR(
14541454
res.error(),
14551455
"Failed to load method %s, error: 0x:%" PRIx32,
@@ -1474,6 +1474,39 @@ struct PyProgram final {
14741474
return std::make_unique<PyMethodMeta>(state_, std::move(res.get()));
14751475
}
14761476

1477+
bool has_etdump() {
1478+
return static_cast<bool>(event_tracer_);
1479+
}
1480+
1481+
void write_etdump_result_to_file(
1482+
const std::string& path,
1483+
const py::object& debug_buffer_path) {
1484+
if (!has_etdump()) {
1485+
throw std::runtime_error("No etdump found");
1486+
}
1487+
auto& etdump = *event_tracer_;
1488+
etdump_result result = etdump.get_etdump_data();
1489+
if (result.buf != nullptr && result.size > 0) {
1490+
write_data_to_file(path, result.buf, result.size);
1491+
free(result.buf);
1492+
if (debug_buffer_size_ > 0 &&
1493+
py::isinstance<py::str>(debug_buffer_path)) {
1494+
// Also write out the debug buffer to a separate file if requested.
1495+
std::string debug_buffer_path_str =
1496+
py::cast<std::string>(debug_buffer_path);
1497+
const auto debug_buffer = get_etdump_debug_buffer();
1498+
write_data_to_file(
1499+
debug_buffer_path_str, debug_buffer.data(), debug_buffer.size());
1500+
}
1501+
} else {
1502+
ET_LOG(
1503+
Info,
1504+
"No etdump data found, try rebuilding with "
1505+
"the CMake option EXECUTORCH_ENABLE_EVENT_TRACER set to ON or with "
1506+
"buck run --config executorch.event_tracer_enabled=true");
1507+
}
1508+
}
1509+
14771510
private:
14781511
std::shared_ptr<ProgramMemory> memory_;
14791512
std::shared_ptr<ProgramState> state_;
@@ -1706,6 +1739,13 @@ PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
17061739
"method_meta",
17071740
&PyProgram::method_meta,
17081741
py::arg("method_name"),
1742+
call_guard)
1743+
.def("has_etdump", &PyProgram::has_etdump, call_guard)
1744+
.def(
1745+
"write_etdump_result_to_file",
1746+
&PyProgram::write_etdump_result_to_file,
1747+
py::arg("path"),
1748+
py::arg("debug_buffer_path") = py::none(),
17091749
call_guard);
17101750
py::class_<PyMethod>(m, "ExecuTorchMethod")
17111751
.def("set_inputs", &PyMethod::set_inputs, py::arg("inputs"), call_guard)

runtime/__init__.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,55 @@
3131
3232
.. code-block:: text
3333
34-
Program methods: ('forward', 'forward2')
34+
Program methods: {'forward'}
3535
Ran forward((tensor([[1., 1.],
3636
[1., 1.]]), tensor([[1., 1.],
3737
[1., 1.]])))
38-
outputs: [tensor([[1., 1.],
39-
[1., 1.]])]
38+
outputs: [tensor([[2., 2.],
39+
[2., 2.]])]
40+
41+
Example usage with ETDump generation:
42+
43+
.. code-block:: python
44+
45+
from pathlib import Path
46+
import os
47+
48+
import torch
49+
from executorch.runtime import Verification, Runtime, Program, Method
50+
51+
# Create program with etdump generation enabled
52+
et_runtime: Runtime = Runtime.get()
53+
program: Program = et_runtime.load_program(
54+
Path("/tmp/program.pte"),
55+
verification=Verification.Minimal,
56+
enable_etdump=True,
57+
debug_buffer_size=1e7, # A large buffer size to ensure that all debug info is captured
58+
)
59+
60+
# Load method and execute
61+
forward: Method = program.load_method("forward")
62+
inputs = (torch.ones(2, 2), torch.ones(2, 2))
63+
outputs = forward.execute(inputs)
64+
65+
# Write etdump result to file
66+
etdump_file = "/tmp/etdump_output.etdp"
67+
debug_file = "/tmp/debug_output.bin"
68+
program.write_etdump_result_to_file(etdump_file, debug_file)
69+
70+
# Check that files were created
71+
print(f"ETDump file created: {os.path.exists(etdump_file)}")
72+
print(f"Debug file created: {os.path.exists(debug_file)}")
73+
print("Directory contents:", os.listdir("/tmp"))
74+
75+
Example output:
76+
77+
.. code-block:: text
78+
79+
Program methods: {'forward'}
80+
ETDump file created: True
81+
Debug file created: True
82+
Directory contents: ['program.pte', 'etdump_output.etdp', 'debug_output.bin']
4083
"""
4184

4285
import functools
@@ -137,6 +180,15 @@ def metadata(self, method_name: str) -> MethodMeta:
137180
"""
138181
return self._program.method_meta(method_name)
139182

183+
def write_etdump_result_to_file(self, etdump_path: str, debug_buffer_path: str) -> None:
184+
"""Writes the etdump and debug result to a file.
185+
186+
Args:
187+
etdump_path: The path to the etdump file.
188+
debug_buffer_path: The path to the debug buffer file.
189+
"""
190+
self._program.write_etdump_result_to_file(etdump_path, debug_buffer_path)
191+
140192

141193
class BackendRegistry:
142194
"""The registry of backends that are available to the runtime."""
@@ -201,6 +253,8 @@ def load_program(
201253
data: Union[bytes, bytearray, BinaryIO, Path, str],
202254
*,
203255
verification: Verification = Verification.InternalConsistency,
256+
enable_etdump: bool = False,
257+
debug_buffer_size: int = 0,
204258
) -> Program:
205259
"""Loads an ExecuTorch program from a PTE binary.
206260
@@ -214,8 +268,8 @@ def load_program(
214268
if isinstance(data, (Path, str)):
215269
p = self._legacy_module._load_program(
216270
str(data),
217-
enable_etdump=False,
218-
debug_buffer_size=0,
271+
enable_etdump=enable_etdump,
272+
debug_buffer_size=debug_buffer_size,
219273
program_verification=verification,
220274
)
221275
return Program(p, data=None)

runtime/test/TARGETS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@ runtime.python_test(
88
deps = [
99
"//executorch/extension/pybindings/test:make_test",
1010
"//executorch/runtime:runtime",
11+
"//executorch/devtools/etdump:serialize",
1112
],
13+
labels = ci.labels(
14+
ci.buckconfig("executorch.event_tracer_enabled", "true"),
15+
),
1216
)

runtime/test/test_runtime.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
ModuleMulti,
1717
)
1818
from executorch.runtime import Runtime, Verification
19-
19+
import os
20+
from executorch.devtools.etdump.serialize import deserialize_from_etdump_flatcc
2021

2122
class RuntimeTest(unittest.TestCase):
2223
def test_smoke(self):
@@ -76,3 +77,63 @@ def test_add(program):
7677
with open(f.name, "rb") as f:
7778
program = runtime.load_program(f.read())
7879
test_add(program)
80+
81+
def test_etdump_generation(self):
82+
"""Test etdump generation by creating a program with etdump enabled and verifying the output."""
83+
84+
ep, inputs = create_program(ModuleAdd())
85+
runtime = Runtime.get()
86+
87+
with tempfile.TemporaryDirectory() as temp_dir:
88+
# Save the program to a file
89+
program_path = os.path.join(temp_dir, "test_program.pte")
90+
with open(program_path, "wb") as f:
91+
f.write(ep.buffer)
92+
93+
# Load program with etdump generation enabled
94+
program = runtime.load_program(
95+
program_path,
96+
verification=Verification.Minimal,
97+
enable_etdump=True,
98+
debug_buffer_size=int(1e7), # Large buffer size to ensure all debug info is captured
99+
)
100+
101+
# Execute the method
102+
method = program.load_method("forward")
103+
outputs = method.execute(inputs)
104+
105+
# Verify the computation is correct
106+
self.assertTrue(torch.allclose(outputs[0], inputs[0] + inputs[1]))
107+
108+
# Write etdump result to files
109+
etdump_path = os.path.join(temp_dir, "etdump_output.etdp")
110+
debug_path = os.path.join(temp_dir, "debug_output.bin")
111+
program.write_etdump_result_to_file(etdump_path, debug_path)
112+
113+
# Check that files were created
114+
self.assertTrue(os.path.exists(etdump_path), f"ETDump file not created at {etdump_path}")
115+
self.assertTrue(os.path.exists(debug_path), f"Debug file not created at {debug_path}")
116+
117+
# Verify the etdump file is not empty
118+
etdump_size = os.path.getsize(etdump_path)
119+
self.assertGreater(etdump_size, 0, "ETDump file is empty")
120+
121+
# Read and deserialize the etdump file to verify its structure
122+
with open(etdump_path, "rb") as f:
123+
etdump_data = f.read()
124+
125+
# Deserialize the etdump and check its header/structure
126+
etdump = deserialize_from_etdump_flatcc(etdump_data)
127+
128+
# Verify ETDump header properties
129+
self.assertIsInstance(etdump.version, int, "ETDump version should be an integer")
130+
self.assertGreaterEqual(etdump.version, 0, "ETDump version should be non-negative")
131+
132+
# Verify run_data structure
133+
self.assertIsInstance(etdump.run_data, list, "ETDump run_data should be a list")
134+
self.assertGreater(len(etdump.run_data), 0, "ETDump should contain at least one run data entry")
135+
136+
# Check the first run_data entry
137+
run_data = etdump.run_data[0]
138+
self.assertIsInstance(run_data.events, list, "Run data should contain events list")
139+
self.assertGreater(len(run_data.events), 0, "Run data should contain at least one events")

0 commit comments

Comments
 (0)