|
| 1 | +# MLX Backend |
| 2 | + |
| 3 | +The MLX delegate is the ExecuTorch backend for Apple Silicon GPUs via the [MLX](https://github.com/ml-explore/mlx) framework. It compiles PyTorch models into a custom FlatBuffer bytecode format at export time and executes them using MLX GPU primitives at runtime. |
| 4 | + |
| 5 | +::::{note} |
| 6 | +The MLX delegate is experimental and under active development. |
| 7 | +:::: |
| 8 | + |
| 9 | +## Features |
| 10 | + |
| 11 | +- GPU acceleration on Apple Silicon (M1 and later) via MLX. |
| 12 | +- INT2/INT4/INT8 weight quantization via [TorchAO](https://github.com/pytorch/ao). |
| 13 | +- Dynamic shape support. |
| 14 | +- Mutable buffers for persistent state across inference calls (e.g., KV cache). |
| 15 | +- Zero-copy constant loading on unified memory. |
| 16 | + |
| 17 | +## Target Requirements |
| 18 | + |
| 19 | +- Apple Silicon Mac (M1 or later) |
| 20 | +- [macOS](https://developer.apple.com/macos) >= 14.0 |
| 21 | + |
| 22 | +## Development Requirements |
| 23 | + |
| 24 | +- [macOS](https://developer.apple.com/macos) on Apple Silicon (M1 or later) |
| 25 | +- [Xcode](https://developer.apple.com/xcode/) (full installation, not just Command Line Tools — the Metal compiler is required) |
| 26 | + |
| 27 | +Verify the Metal compiler is available: |
| 28 | + |
| 29 | +```bash |
| 30 | +xcrun -sdk macosx --find metal |
| 31 | +``` |
| 32 | + |
| 33 | +If this prints a path (e.g., `/Applications/Xcode.app/.../metal`), you're set. If it errors, install Xcode from [developer.apple.com](https://developer.apple.com/xcode/), then switch the active developer directory: |
| 34 | + |
| 35 | +```bash |
| 36 | +sudo xcode-select -s /Applications/Xcode.app/Contents/Developer |
| 37 | +``` |
| 38 | + |
| 39 | +---- |
| 40 | + |
| 41 | +## Using the MLX Backend |
| 42 | + |
| 43 | +To target the MLX backend during export and lowering, pass an instance of `MLXPartitioner` to `to_edge_transform_and_lower`. The MLX backend also provides a set of graph optimization passes via `get_default_passes()` that should be passed as `transform_passes`. The example below demonstrates this process using MobileNet V2: |
| 44 | + |
| 45 | +```python |
| 46 | +import torch |
| 47 | +import torchvision.models as models |
| 48 | +from torchvision.models.mobilenetv2 import MobileNet_V2_Weights |
| 49 | +from executorch.backends.mlx import MLXPartitioner |
| 50 | +from executorch.backends.mlx.passes import get_default_passes |
| 51 | +from executorch.exir import to_edge_transform_and_lower |
| 52 | + |
| 53 | +mobilenet_v2 = models.mobilenetv2.mobilenet_v2(weights=MobileNet_V2_Weights.DEFAULT).eval() |
| 54 | +sample_inputs = (torch.randn(1, 3, 224, 224), ) |
| 55 | + |
| 56 | +et_program = to_edge_transform_and_lower( |
| 57 | + torch.export.export(mobilenet_v2, sample_inputs), |
| 58 | + transform_passes=get_default_passes(), |
| 59 | + partitioner=[MLXPartitioner()], |
| 60 | +).to_executorch() |
| 61 | + |
| 62 | +with open("mv2_mlx.pte", "wb") as file: |
| 63 | + et_program.write_to_file(file) |
| 64 | +``` |
| 65 | + |
| 66 | +`get_default_passes()` includes RMSNorm fusion, consecutive view/permute/dtype-cast collapsing, no-op removal, and common subexpression elimination. These are recommended for all models and required for optimal LLM performance. |
| 67 | + |
| 68 | +::::{note} |
| 69 | +The MLX backend is primarily designed for LLM and generative AI workloads on Apple Silicon. The MobileNet V2 example above is shown for simplicity, but in practice you would use this backend for models like Llama, Whisper, and other transformer-based architectures. See [LLM example](https://github.com/pytorch/executorch/tree/main/backends/mlx/examples/llm) for a more representative use case. |
| 70 | +:::: |
| 71 | + |
| 72 | +See [Partitioner API](mlx-partitioner.md) for a reference on available partitioner options. |
| 73 | + |
| 74 | +---- |
| 75 | + |
| 76 | +## Quantization |
| 77 | + |
| 78 | +The MLX backend supports INT4, INT8, and NVFP4 weight quantization via TorchAO for both linear and embedding layers. This is particularly useful for LLM inference. See [MLX Quantization](mlx-quantization.md) for details. |
| 79 | + |
| 80 | +---- |
| 81 | + |
| 82 | +## Runtime Integration |
| 83 | + |
| 84 | +### Python (pybindings) |
| 85 | + |
| 86 | +The simplest way to get started is to install ExecuTorch with Python bindings. From the repo root: |
| 87 | + |
| 88 | +```bash |
| 89 | +python install_executorch.py |
| 90 | +``` |
| 91 | + |
| 92 | +On Apple Silicon, when the Metal compiler is available, the MLX backend is automatically included. You can then export models in Python using the MLX partitioner and run them via the ExecuTorch Python API. |
| 93 | + |
| 94 | +### C++ (CMake preset) |
| 95 | + |
| 96 | +To build the C++ runtime with the MLX delegate, use the `mlx-release` CMake workflow preset from the repo root: |
| 97 | + |
| 98 | +```bash |
| 99 | +cmake --workflow --preset mlx-release |
| 100 | +``` |
| 101 | + |
| 102 | +This configures and builds a Release build of the ExecuTorch runtime with the MLX delegate and installs artifacts into `cmake-out/`. The preset enables the MLX delegate along with commonly needed extensions (module, data loader, flat tensor, LLM runner, etc.). |
| 103 | + |
| 104 | +Downstream C++ apps can then `find_package(executorch)` and link against `mlxdelegate` and `mlx`. The `executorch_target_link_options_shared_lib` utility handles whole-archive linkage (required for static initializer registration) cross-platform, and `executorch_target_copy_mlx_metallib` copies the Metal kernel library next to the binary so MLX can find it at runtime: |
| 105 | + |
| 106 | +```cmake |
| 107 | +# CMakeLists.txt |
| 108 | +find_package(executorch REQUIRED) |
| 109 | +
|
| 110 | +# Link MLX delegate (with whole-archive for static initializer registration) |
| 111 | +target_link_libraries(my_target PRIVATE mlxdelegate mlx) |
| 112 | +executorch_target_link_options_shared_lib(mlxdelegate) |
| 113 | +
|
| 114 | +# Copy mlx.metallib next to the binary for runtime |
| 115 | +executorch_target_copy_mlx_metallib(my_target) |
| 116 | +``` |
| 117 | + |
| 118 | +No additional steps are necessary to use the backend beyond linking the target. An MLX-delegated `.pte` file will automatically run on the registered backend. |
| 119 | + |
| 120 | +There is also an `mlx-debug` preset useful during development: |
| 121 | + |
| 122 | +```bash |
| 123 | +cmake --workflow --preset mlx-debug |
| 124 | +``` |
| 125 | + |
| 126 | +## Reference |
| 127 | + |
| 128 | +**→{doc}`/backends/mlx/mlx-troubleshooting` — Debug common issues.** |
| 129 | + |
| 130 | +**→{doc}`/backends/mlx/mlx-partitioner` — Partitioner options.** |
| 131 | + |
| 132 | +**→{doc}`/backends/mlx/mlx-quantization` — Supported quantization schemes.** |
| 133 | + |
| 134 | +**→{doc}`/backends/mlx/mlx-op-support` — Supported operators.** |
| 135 | + |
| 136 | +```{toctree} |
| 137 | +:maxdepth: 2 |
| 138 | +:hidden: |
| 139 | +:caption: MLX Backend |
| 140 | +mlx-troubleshooting |
| 141 | +mlx-partitioner |
| 142 | +mlx-quantization |
| 143 | +mlx-op-support |
| 144 | +``` |
0 commit comments