mlx-local-server: OpenAI-Compatible Inference in 8 MB of Rust

June 1, 2025 · 7 min read

How I built a single Rust binary that serves LLM chat, image generation, and audio on Apple Silicon — with an 8 MB idle footprint, 16 ms cold start, and full OpenAI API compatibility. Showcased at WWDC 2025.

The Problem: Local Inference Servers Are Too Heavy

Every local LLM server I tried had the same problem: they were built for Python-first workflows. Ollama, LM Studio, llama.cpp — all great tools, but they carry hundreds of megabytes of idle overhead. If you want to run a local agent that spins up an inference server on demand, that startup tax is brutal.

I wanted something different: a server that could start in under 20 milliseconds, idle at under 10 MB, and still speak the OpenAI API dialect that every agent framework expects. And since I was targeting Apple Silicon exclusively, MLX was the obvious inference backend — it's Apple's own ML framework, purpose-built for the unified memory architecture of M-series chips.

Why Rust + PyO3?

The constraint was real-time responsiveness at the Rust layer with Python/MLX doing the heavy lifting. PyO3 lets you call Python from Rust (or expose Rust to Python) with almost no overhead. The server binary embeds a Python interpreter, loads MLX at startup, and streams tokens back through Rust's async runtime.

The result: Rust handles all HTTP routing, request parsing, streaming SSE, and concurrency. Python handles exactly one thing — running the model forward pass. This split gives you the performance characteristics of Rust for everything except the GPU kernel calls, which are going to dominate latency anyway.

// Simplified: calling MLX generate from Rust via PyO3
Python::with_gil(|py| {
    let mlx = PyModule::import(py, "mlx_bridge")?;
    let stream = mlx.call_method1("stream_tokens", (prompt, max_tokens))?;
    // ... stream to SSE response
})

Three Servers, One Binary

The binary routes to three distinct backends depending on the endpoint:

Each server starts independently. You can run just the LLM server if you don't need image generation, and the binary footprint stays at 8 MB idle — the MLX Python env is lazy-loaded on first inference.

16 ms Cold Start

The cold start number surprised me too. The key is that "cold start" here means "HTTP server accepting connections" — not "model loaded." The Rust HTTP layer starts in under 16 ms. Model loading (the first time you call /v1/models/load) takes longer, but for agent use cases, you typically pre-load the model once at session start, then the server stays warm for the session lifetime.

For truly cold starts where the model also needs to load, quantized 4-bit models on M-series chips load in 2–4 seconds from local storage — still dramatically faster than cloud API round-trips for long sessions.

WWDC 2025

The project was featured in WWDC 2025 — Build local AI agents on Mac with MLX. It was a weird feeling watching Apple engineers demo a workflow that's almost identical to what I built independently — confirms the design is solid.

Source code is on GitHub. The README has a quick-start that gets you from zero to streaming LLM output in under 5 minutes on any M-series Mac.

Tags: rust, mlx, apple-silicon, llm, inference, open-source

By Arnav Gupta. More posts at Gists & Blogs.