Open-source project
pytorch/executorch avatar
pytorch/executorch

ExecuTorch: PyTorch's AOT Path from torch.export to a .pte on Device

On-device AI across mobile, embedded and edge for PyTorch

5,028 stars1,158 forksPythonNOASSERTION

At a glance

What is it?
ExecuTorch compiles PyTorch models ahead of time into a .pte file and runs them through a small C++ runtime on mobile, embedded and edge hardware. The export step is clean; the hard part is the backend toolchain you have to assemble around it.
Who is it for?
Adopt ExecuTorch if your model already trains in PyTorch and you want to keep one export path across Android, iOS and embedded targets, and if you can afford to build and ship a backend toolchain alongside your app. Do not adopt it if you need a single command that produces a working binary for your exact board, or if you cannot take on the partitioner and delegate work that a specific NPU requires.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem ExecuTorch is aimed at: PyTorch models that have to leave the server

A model that runs on a phone or a microcontroller cannot be a pickle of Python objects. It has to be a file with a fixed operator set, a runtime small enough to fit the device, and a path to whatever accelerator the device happens to carry. The usual answer is to convert the model into some other interchange format and rebuild the inference graph by hand. ExecuTorch's stated position is that you skip that: the README says there is no .onnx or .tflite step, and no manual C++ rewrite. The audience is therefore narrow and specific. It is a team that already trains in PyTorch, wants to ship inference to Android, iOS or an embedded target, and does not want to maintain a separate graph representation for each platform. Meta's own deployments are listed in the README (Instagram, WhatsApp, Quest 3, Ray-Ban Meta Smart Glasses), which tells you the intended scale is production mobile, not a hobby board. If your model never leaves a server, none of this applies to you.

Ahead-of-time compilation: export, lower, then load a .pte

The mechanism is a three stage pipeline, and the README is explicit that it is ahead-of-time rather than a JIT. First, torch.export captures the model graph. Second, to_edge_transform_and_lower quantizes, optimizes and partitions that graph, emitting a .pte buffer. Third, the on-device C++ runtime loads the .pte and executes methods by name. The intermediate representation is a standardized Core ATen operator set, which is what makes the runtime small: it implements a known set of ops rather than arbitrary Python. Partitioners are the interesting part of the design. A partitioner hands subgraphs to specialized hardware such as an NPU or GPU, and anything it cannot claim falls back to CPU. That fallback is a convenience and a hazard at the same time. A model can export successfully, produce a .pte, and still run most of its work on CPU because the partitioner rejected the shapes it did not like. The README's claim that you switch hardware targets with a single line change is true at the level of swapping XnnpackPartitioner for CoreMLPartitioner or QnnPartitioner. It is not a promise that the resulting split is efficient on every backend.

Getting a model out: the export code and the CLI paths

Installation is one command, pip install executorch, with the caveat that backend export tools pull optional dependency groups. The README gives exactly one example of that syntax: pip install 'executorch[ethos_u]' for Ethos-U AOT export. It also states plainly that embedded toolchains, simulators and target runtimes are installed separately, which is the sentence to read twice before promising a delivery date. The Python export path is short. You call torch.export.export on an eval-mode model with example inputs, pass the result to to_edge_transform_and_lower with a partitioner list, call .to_executorch(), and write program.buffer to a file such as model.pte. The README also shows an optional local check through the pybind runtime: Runtime.get(), load_program("model.pte"), load_method("forward"), then execute a tensor. For large language models there are two documented routes. The first is python -m executorch.extension.llm.export.export_llm --model llama3_2 --output llama.pte. The second is Optimum-ExecuTorch, invoked as optimum-cli export executorch with --model, --task, --recipe xnnpack and --output_dir. On the device side the README shows three bindings: C++ via executorch/extension/module/module.h and Module("model.pte"), Swift via import ExecuTorch and Module(filePath:), and Kotlin via Module.load("model.pte") with Tensor.fromBlob. All three take the same .pte and expose forward().

Where the abstraction leaks: partitioners, toolchains and the 50KB figure

The 50KB base footprint quoted in the README refers to the runtime, not to your shipped artifact. A .pte carrying a quantized vision model or an LLM is far larger, and the accelerators you delegate to usually bring their own shared libraries. Treat the number as a property of the execution engine, not a budget for the app. The more consequential limitation is the toolchain. Partitioning to a vendor NPU generally means that vendor's compiler or SDK is in the loop, and the README already concedes that embedded toolchains and simulators are separate installs. That turns a Python export script into a build environment problem: cross-compilers, SDK versions, and a CI job that has to reproduce all of it. The second leak is operator coverage. Because the runtime targets a Core ATen operator set and delegates the rest, an op that no partitioner claims and no CPU kernel implements is a failure at export or at load, not at training time. The README does not enumerate which ops fall outside that set, so the only reliable check is to export your specific model and inspect what the partitioner actually took. A third, smaller friction point: the runtime exposes named methods, so the README's examples all load "forward". Models with several entry points need those names tracked through export.

How this differs from ONNX Runtime and TFLite

The obvious alternative is ONNX Runtime or TensorFlow Lite, and the difference is not performance, it is where the graph comes from. Those stacks assume you convert into their own format first, and then apply their quantization and delegation tooling. ExecuTorch keeps the source of truth in torch.export and lowers from there, so the graph you debug in PyTorch is the graph that gets partitioned. If your training stack is PyTorch and your team already reasons about ATen ops, that removes an entire translation layer and the class of bugs that comes with it. If your training stack is not PyTorch, the argument collapses: you would be converting into PyTorch just to convert out again, and ONNX Runtime or TFLite is the shorter path. There is a second, subtler difference. ONNX Runtime's execution providers and ExecuTorch's partitioners are solving the same problem, but ExecuTorch's partitioning happens ahead of time and is baked into the .pte, whereas an execution provider can be selected at runtime on the host. Baking the split in gives you a smaller, more predictable runtime. It also means a .pte is tied to the backend configuration it was exported with, so a single artifact does not automatically cover every device in your fleet.

Version cadence, maintenance and the licence question

The release history in the repository metadata shows v1.4.0 and v1.4.1 within about a week of each other in August 2026, following v1.3.1 in late May 2026. A patch release landing days after a minor release is normal for a project of this size, but it does mean pinning matters: executorch on PyPI is the package your export script imports, and the .pte format plus the runtime are versioned together. A .pte produced by one version is not guaranteed to load in a runtime built from another, so the export job and the device build should be pinned to the same release and upgraded as one unit. That is the real maintenance cost here, not the Python dependency. On licensing, the repository metadata reports NOASSERTION rather than a recognised SPDX identifier, and the README's licence section is not included in the material available. Do not assume a permissive licence from the PyTorch name. Read the LICENSE file in the repository and, if you are shipping in a product, have counsel confirm the terms for the runtime you link into your app. Nothing here is legal advice.

Who should take this on, and what to check before the first commit

The fit is a PyTorch team shipping to heterogeneous edge hardware that wants one export path and is willing to own a backend build. The misfit is a team that needs a turnkey binary for one specific board, or that cannot absorb vendor SDKs into CI. The first thing to verify is not the export script, it is the partitioner for your target silicon: confirm it exists, confirm which optional dependency group it needs, and confirm what fraction of your graph it claims. Export one representative model, write the .pte, load it through the pybind Runtime, and compare outputs against the eager model before you touch a device. Then check the version pairing between the PyPI package and the runtime you intend to build, and read the LICENSE file rather than trusting the NOASSERTION metadata. If the partitioner claims your graph and the outputs match, the remaining work is build engineering, which is a known cost. If the partitioner claims almost nothing, you are shipping a CPU model with extra steps.

Editorial conclusion

Adopt ExecuTorch if your model already trains in PyTorch and you want to keep one export path across Android, iOS and embedded targets, and if you can afford to build and ship a backend toolchain alongside your app. Do not adopt it if you need a single command that produces a working binary for your exact board, or if you cannot take on the partitioner and delegate work that a specific NPU requires. Before committing, verify three things: which partitioner actually matches your silicon, what optional dependency group that partitioner needs (the README names executorch[ethos_u] as one example), and the exact licence terms of the repository, since the metadata reports NOASSERTION rather than a recognised identifier.

Official sources

  1. Issues
  2. Project website
  3. pytorch/executorch on GitHub
  4. README
  5. Releases
Community notes

Community notes