exo: Automatic Partitioning Across Your Macs
The previous lesson was the mechanism: host files, backends, ranks, a flag that chooses a split. exo is the same mechanism with the ceremony removed. You start it on each Mac, it finds the others, it looks at what they have and how they are connected, it proposes ways to place a model across them, and it serves whichever one you pick behind an OpenAI-compatible endpoint.
By the end of this lesson you should be able to say what exo automates and what it does not, read
one of its placement previews and understand what it is telling you, decide between exo and
mlx.launch for a given job, and state the project’s platform support and status with a date
rather than from memory.
What exo is
Section titled “What exo is”exo is an Apache-2.0 licensed clustering runtime for local models, maintained by exo labs. Its own one-line summary is that it “connects all your devices into an AI cluster”, and the README’s claim about why you would want that is worth reading as two separate statements: that it enables “running models larger than would fit on a single device”, which is capacity, and that RDMA support “makes models run faster as you add more devices”, which is throughput. Part 18 spent a lesson separating those two reasons, and exo’s README is a good example of how easily they get bundled.
Underneath, it is not a new inference engine. The README states that exo “uses MLX as an inference backend and MLX distributed for distributed communication”. Everything in the previous lesson is still what is happening; exo decides the arguments for you.
Discovery: the part that is genuinely automatic
Section titled “Discovery: the part that is genuinely automatic”The README’s first feature is the one that changes the experience most: “Devices running exo automatically discover each other - no manual configuration.” There is no host file, no list of addresses, no rank numbering. You run the same command on every machine.
Fragment — not complete on its own
uv run exoTwo details from the argument parser matter for a household with more than one experiment in it.
Discovery is namespaced: --namespace sets a “discovery namespace, nodes with different namespaces
will not connect”, and the README describes the same idea in the macOS application as an
EXO_LIBP2P_NAMESPACE setting whose use cases are “running multiple separate exo clusters on the
same network” and “preventing accidental cluster joining”. The ports are fixed and settable:
--api-port (default 52415), --zenoh-port (52414) and --discovery-port (52413).
A node can also be told what job it is doing. --no-worker runs “exo without the worker
component”, which the README recommends for “coordinator-only nodes that handle networking and
orchestration but don’t execute inference tasks”, and --no-downloads disables the download
coordinator so a node never fetches weights. On a home cluster the interesting case is a small
machine with a good network position acting as the front door for two large machines behind it.
Topology-aware placement
Section titled “Topology-aware placement”The second feature is the one that replaces your judgement. The README describes it as: “exo figures out the best way to split your model across all available devices based on a realtime view of your device topology. It takes into account device resources and network latency/bandwidth between each link.”
That sentence is doing what Part 18 asked you to do by hand. Which machines have enough memory, which links are fast enough for a split that talks at every layer, and which are only fast enough for a split that talks at layer boundaries: exo answers those from a live view rather than from your notebook.
From a model name to a served endpoint
- DiscoveryEvery node running exo finds the others in its namespace. No host file, no ranks.
- TopologyResources per device and latency and bandwidth per link, kept current rather than measured once.
- PreviewsGET /instance/previews returns every valid placement for a model: which nodes, which sharding, which transport.
- You choosePick a placement. This is the one judgement exo does not make for you, and the interesting one.
- Create the instancePOST /instance with the placement, then wait on GET /instance/await until it reports ready.
- ServeRequests arrive at any node on port 52415 in OpenAI, Claude, Responses or Ollama dialect.
- Delete the instanceDELETE /instance/{id} frees the memory. Instances are explicit objects, not a side effect of asking a question.
Reading a placement preview
Section titled “Reading a placement preview”The preview endpoint is the most instructive thing in exo’s API, because it makes the partitioner
show its work. GET /instance/previews?model_id=... “will preview all valid placements for your
model”, and the README’s sample response has the fields that matter:
Pseudocode — not a real command
{ "previews": [ { "model_id": "<the MLX repository>", "sharding": "Pipeline", # or Tensor "instance_meta": "MlxRing", # the transport, ring or jaccl "instance": { ... }, # the whole placement, to be posted back "memory_delta_by_node": { ... }, # what this placement costs each node "error": null # or why this placement will not work } ]}Four things are being decided at once and shown separately: which nodes take part, whether the
split is pipeline or tensor, which transport carries it, and what it will cost each node in
memory. A preview with a non-null error is exo telling you why a placement you might have
expected is not available, which is more useful than silence.
The related GET /instance/placement computes a placement without creating it and accepts
model_id, sharding, instance_meta and min_nodes as query parameters, so you can ask for a
specific shape rather than choosing from what is offered.
The transports underneath
Section titled “The transports underneath”instance_meta in a preview is the same choice the previous lesson made with --backend. exo’s
benchmarking tool spells the two values plainly in its filter: ring or jaccl. So a placement is
a pipeline or tensor split, carried by the MLX ring backend over whatever network exists, or by
JACCL over Thunderbolt RDMA.
Everything the previous lesson said about JACCL therefore applies here unchanged, and exo’s README
repeats the requirements in its own words. RDMA “is a new capability added to macOS 26.2. It works
on any Mac with Thunderbolt 5”, enabled by booting into Recovery and running rdma_ctl enable. The
caveats section adds four practical ones: every device in an RDMA cluster “must be connected to all
other devices in the cluster”; “the cables must support TB5”; on a Mac Studio “you cannot use the
Thunderbolt 5 port next to the Ethernet port”; and the operating system versions must “match
exactly (even beta version numbers) on all devices”.
The README also documents a setup script that “will disable Thunderbolt Bridge and set dhcp on each
RDMA port”, which is the same bridge-teardown step mlx.distributed_config performs. Two projects
independently arriving at “turn off the bridge macOS gives you” is a good sign that it is not
optional.
The API surface
Section titled “The API surface”exo speaks four dialects on one port, which is the feature that makes it drop into an existing
setup. The README lists compatibility with “OpenAI Chat Completions API, Claude Messages API,
OpenAI Responses API, and Ollama API”, and the endpoint summary in the API reference confirms
/v1/chat/completions, /v1/messages, /v1/responses and a family of /ollama/api/... paths,
alongside /models, /v1/models, /models/search and /models/add.
For this course the important extra endpoint is POST /bench/chat/completions, which is “same as
/v1/chat/completions, but also returns performance and generation statistics” including
prompt_tps, generation_tps, prompt_tokens, generation_tokens and peak_memory_usage. That
is prefill speed, decode speed and peak memory reported by the server itself, per request, which is
exactly what this part’s lab needs to record and what a plain OpenAI-compatible endpoint makes you
compute from timings.
Installing it, and which platform you are on
Section titled “Installing it, and which platform you are on”There are two supported routes on macOS. The macOS application is a download or
brew install --cask exo, and the README states that it “requires macOS Tahoe 26.2 or later”. From
source, the sequence is a clone, a dashboard build with npm, uv sync --extra mlx and uv run exo,
with Xcode, Homebrew, uv, node and a Rust nightly toolchain as prerequisites. The README notes that
the application “will ask for permission to modify system settings and install a new Network
profile”, which is the Thunderbolt reconfiguration described above.
Linux is supported and limited. The README is unambiguous: “Currently, exo runs on CPU on Linux. GPU support for Linux platforms is under development.” A Linux node can therefore join a cluster, serve the API and do useful coordination work, but it will not contribute GPU inference.
Measuring it
Section titled “Measuring it”exo ships its own benchmark driver, which is unusual and useful. bench/exo_bench.py “measures
model prefill and token generation speed across different placement configurations”, driving the
/bench/chat/completions endpoint. Its parameters are the ones you would want: --model, --pp
for prompt sizes, --tg for generation lengths, --max-nodes, --instance-meta to filter by
ring, jaccl or both, --sharding to filter by pipeline, tensor or both, --repeat,
--warmup and --json-out.
That option list is a good summary of the whole part. The two axes that decide whether a Mac cluster is worth having are the transport and the split, and exo’s own benchmark tool is built to sweep exactly those two.
Limits, and when to use mlx.launch directly
Section titled “Limits, and when to use mlx.launch directly”exo automates the placement, and automation is the right default when the placement is not the thing you are studying. Four situations argue the other way.
You are learning the mechanism. A host file you wrote yourself is a topology you understand.
The previous lesson exists because a cluster that fails is much easier to debug when you know what
ssh, ips and rdma meant in the file.
You need a model or an option exo does not surface. mlx_lm.server has flags for prompt
caching, draft models, concurrency and chat templates that a placement API does not expose. If your
requirement is a specific mlx-lm feature, drive mlx-lm.
You want the comparison to be clean. Measuring ring against JACCL, or pipeline against tensor, on the same model and the same pair of machines is easiest when nothing is choosing for you. exo’s own benchmark filters exist for this reason, and they are the exo-shaped version of the same discipline.
The project’s status matters to you. exo is young, moves quickly, and its RDMA support arrived alongside the operating system feature it depends on. Jeff Geerling’s write-up of a four-machine Mac Studio cluster, published 2025-12-18, is candid about that: he was “working with prerelease software while testing”, and reports that “it was obvious RDMA over Thunderbolt is new. When it works, it works great. When it doesn’t…” Treat that as the current state of a moving target rather than a verdict, and check the project’s issues before planning around it.
The same two Macs, two ways to drive them
- workerMac Aexo node, or mlx.launch rank 0
- workerMac Bexo node, or mlx.launch rank 1
- clientYour clientOpenAI-compatible request
- Mac A connected to Mac BThunderbolt 5: ring over TCP, or JACCL over RDMA
- Your client connected to Mac Aexo: port 52415 on any node. mlx-lm: the port you gave mlx_lm.server on rank 0
- Mac A connected to Mac Bhouse Ethernet: exo discovery, or the ssh mlx.launch uses to start ranks
Inspect the placement that automation selected
Section titled “Inspect the placement that automation selected”Automatic discovery and partitioning remove manual steps, but they do not remove the need to inspect the result. Record discovered hosts, available memory, selected model, partition assignment and transport before measuring. A node that appears in discovery may not receive a useful share of the computation.
Compare the automatically selected placement with a single-machine baseline for a model that fits locally. Then, if capacity is the goal, test the larger model separately. Keep prompt lengths and answer limits explicit; otherwise a change in output behaviour can look like a placement improvement.
Observe what happens when a node becomes unavailable during an expendable request and after the service is idle. Discovery recovery and successful recovery of an in-flight distributed computation are different properties. Save the operator steps required to restore service. Automation is valuable when it makes a supported topology easier to operate, but the acceptance criteria remain model correctness, resource use, latency and recoverability on your actual machines.
exo is an Apache-2.0 clustering runtime that discovers the Macs on your network, builds a live view of their resources and links, proposes valid placements for a model, and serves whichever you choose behind OpenAI, Claude, Responses and Ollama-compatible endpoints on port 52415. It is not its own engine: it runs on MLX and MLX distributed, so the ring and JACCL backends from the previous lesson are what actually move the tensors.
Its placement previews are worth reading even if you never use exo, because they name the four decisions a partitioner makes: nodes, sharding, transport and memory cost per node. Instances are explicit and asynchronous: create, await, use, delete.
Its RDMA requirements are macOS 26.2 or later, Thunderbolt 5, a Recovery-mode enable step, a full
mesh of cables, matching operating system builds, and on a Mac Studio one port that cannot be used.
On Linux it runs on CPU only, which makes llama.cpp RPC the better cross-platform choice on the
other three tracks. Reach for mlx.launch directly when you are learning, when you need an mlx-lm
feature exo does not expose, or when a clean comparison is the point.
Check your understanding
Sources for this lesson
5 verified · checked 2026-09-09
- 01exo - README§ Features; Quick Start; Enabling RDMA on macOS; Environment Variables; Benchmarking; Hardware Accelerator Supportgithub.com/exo-explore/exo2026-09-09
- 02exo - API technical reference§ Instance Management; Inference; Complete Endpoint Summarygithub.com/exo-explore/exo/blob/main/docs/api.md2026-09-09
- 03exo source - src/exo/main.py§ argument parsergithub.com/exo-explore/exo/blob/main/src/exo/main.py2026-09-09
- 04MLX documentation - Distributed Communication§ Backends; Getting Started with JACCLml-explore.github.io/mlx/build/html/usage/distributed.html2026-09-09
- 051.5 TB of VRAM on Mac Studio - RDMA over Thunderbolt 5§ Enabling RDMA; Stability Issuesjeffgeerling.com/blog/2025/15-tb-vram-on-mac-studio-rdma-over-thunderbolt-52026-09-09
Every technical claim on this page was checked against the official documentation of the tool, vendor or model publisher on the date shown, at the version pinned for the course. Where the course disagrees with folklore, the source is how you can tell which one to trust.