MLX-LM Server vs llama-server: Which Local Inference Server for Apple Silicon (2026)
If you have decided to host an LLM on Apple Silicon and you have already picked your runtime (MLX or llama.cpp), the next question is the server. Both projects ship an HTTP server that speaks the OpenAI API: mlx_lm.server from the MLX-LM project, and llama-server from llama.cpp. Either one will turn a loaded model into a /v1/chat/completions endpoint your backend can call. The interesting question is which one belongs in your production stack.
If you have decided to host an LLM on Apple Silicon and you have already picked your runtime (MLX or llama.cpp), the next question is the server. Both projects ship an HTTP server that speaks the OpenAI API: mlx_lm.server from the MLX-LM project, and llama-server from llama.cpp. Either one will turn a loaded model into a /v1/chat/completions endpoint your backend can call. The interesting question is which one belongs in your production stack.
The framework comparison (MLX vs llama.cpp as inference engines) is well understood. The server comparison is not. The servers diverge on concurrency, streaming behavior, quantization formats, memory residency, and operational ergonomics in ways that matter once you put real traffic through them.
We ran both servers on M4 Pro (24 GB) and M5 Pro (48 GB) Mac minis with Llama 3.1 8B, Mistral Small 24B, and Llama 3.3 70B at matched quantization levels (Q4_K_M for llama-server, 4-bit GPTQ via MLX for mlx_lm.server). Here is what the data and the operational experience actually say.
The Servers at a Glance
| Dimension | mlx_lm.server (MLX-LM) | llama-server (llama.cpp) |
|---|---|---|
| Backend | MLX (Metal, native Apple Silicon) | llama.cpp (Metal backend, also CPU/CUDA) |
| API surface | OpenAI Chat Completions, Completions | OpenAI Chat, Completions, embeddings, reranking |
| Model format | MLX (safetensors), HF AutoConvert | GGUF only |
| Quantization | 2, 3, 4, 6, 8 bit (MLX group-wise) | Q2_K through Q8_0, K-quants, IQ-quants |
| Concurrent requests | Sequential by default; experimental batching | Continuous batching, parallel slots |
| Streaming | SSE, token-by-token | SSE, token-by-token, with logprobs |
| Function calling | Manual via prompt templates | Grammar-constrained sampling (GBNF) |
| Memory residency | Loaded once, persistent | Loaded once, with mmap for fast cold starts |
| Stable release | mlx-lm 0.21 (May 2026) | llama.cpp b4500+ (May 2026) |
Both speak enough of the OpenAI spec to be drop-in compatible with the Python and TypeScript SDKs. Past that point, the servers behave quite differently.
Throughput: M4 Pro and M5 Pro Numbers
We ran the same evaluation harness against both servers: 200 prompts averaging 1,200 input tokens and 350 output tokens, prompts replayed sequentially and then at concurrency 4. Numbers are tokens per second of generation, averaged across the 200 prompts after a warm-up run.
Llama 3.1 8B, single request (sequential)
| Hardware | mlx_lm.server (4-bit) | llama-server (Q4_K_M) |
|---|---|---|
| M4 Pro (24 GB) | 62 tok/s | 58 tok/s |
| M5 Pro (48 GB) | 81 tok/s | 74 tok/s |
MLX wins by roughly 7 to 10 percent on single-request 8B inference. The Metal kernels in MLX are tuned aggressively for the M-series neural cores, and the gap widens slightly on M5 Pro because MLX exploits the increased GPU core count more efficiently.
Llama 3.1 8B, concurrency 4
| Hardware | mlx_lm.server | llama-server |
|---|---|---|
| M4 Pro (24 GB) | 71 tok/s aggregate | 162 tok/s aggregate |
| M5 Pro (48 GB) | 94 tok/s aggregate | 218 tok/s aggregate |
This is the chart that reverses the conclusion. At concurrency 4, llama-server continuous batching nearly doubles aggregate throughput, while mlx_lm.server (without experimental batching enabled) serializes requests and barely improves on single-request numbers. If your workload has any concurrency at all, llama-server wins on raw throughput.
Llama 3.3 70B, Q4 / 4-bit, M5 Pro only
| Server | Single request | Concurrency 2 |
|---|---|---|
| mlx_lm.server | 8.4 tok/s | 9.1 tok/s aggregate |
| llama-server | 7.9 tok/s | 14.6 tok/s aggregate |
At 70B, both servers are memory-bandwidth-bound. Single-request performance is close, with MLX slightly ahead. Continuous batching in llama-server still gives a clear advantage under concurrent load even at the high end of what M5 Pro can run.
Mistral Small 24B, 4-bit, M5 Pro
| Server | Single request | Concurrency 4 |
|---|---|---|
| mlx_lm.server | 28 tok/s | 31 tok/s aggregate |
| llama-server | 26 tok/s | 79 tok/s aggregate |
Same pattern. MLX wins single-request by a small margin. llama-server wins concurrent throughput by a wide margin.
Cold Start and Model Loading
llama-server uses memory-mapped I/O on GGUF files. First request after launch is fast (typically under 2 seconds for an 8B model, 10 to 14 seconds for a 70B), and the OS page cache keeps the model warm across restarts.
mlx_lm.server loads safetensors into MLX arrays at startup. For an 8B model on M5 Pro, expect 8 to 12 seconds of cold start. For 70B, expect 60 to 90 seconds. There is no equivalent of mmap-backed lazy loading. If you cycle the server frequently (autoscaling, blue-green deploys), this matters.
Quantization Format Reality
This is where the operational story gets uneven. llama.cpp has an ecosystem of GGUF quantizations on Hugging Face spanning thousands of models. K-quants (Q4_K_M, Q5_K_S) and IQ-quants (IQ4_XS, IQ3_M) give fine-grained control over the quality/size tradeoff. For most popular models, someone has already quantized GGUF variants you can pull and run.
MLX-LM converts Hugging Face safetensors on the fly or loads pre-quantized MLX models. The MLX model hub on Hugging Face has good coverage of Llama, Mistral, Qwen, and Gemma families, but it lags GGUF for less popular or experimental models. Conversion is straightforward (mlx_lm.convert), but it adds friction.
Quality at matched bit width is roughly comparable. MLX 4-bit group-wise quantization produces output quality similar to GGUF Q4_K_M on most benchmarks (within 1 to 2 percentage points on MMLU and GSM8K). Neither has a quality crown across the board.
API Surface and Tooling
llama-server exposes more endpoints. Beyond chat completions, it supports:
/v1/embeddingsfor embedding models loaded alongside the chat model/v1/rerankfor reranking with cross-encoder GGUF models/healthand/metrics(Prometheus format)- Grammar-constrained sampling for forced JSON output (GBNF)
- Speculative decoding using a draft model
mlx_lm.server exposes chat completions, completions, and a model list endpoint. No embeddings, no reranking, no native grammars, no built-in metrics. If you want function calling, you handle it at the application layer with prompt templates and JSON parsing.
For a backend that needs an LLM and an embedding model and a reranker (a typical RAG stack), llama-server collapses three services into one process. With mlx_lm.server, you run separate processes or move embeddings to a different stack entirely.
When mlx_lm.server Is the Right Choice
Pick mlx_lm.server when:
- You are running single-user or low-concurrency workloads (one developer, a personal agent, a thin desktop app like Switchboard or Stat Sniper's internal tooling) where peak single-request throughput matters more than aggregate throughput.
- You are evaluating MLX-native features (LoRA adapters, fine-tuning workflows, MLX quantization research) and want the server in the same toolchain.
- You need the simplest possible install on a developer Mac.
pip install mlx-lmandpython -m mlx_lm.serveris two commands. - You are running models that already exist as MLX safetensors and have no need for the GGUF ecosystem.
When llama-server Is the Right Choice
Pick llama-server when:
- You are exposing inference as a backend service with any meaningful concurrency. Continuous batching is the killer feature.
- You need embeddings or reranking in the same process for RAG pipelines.
- You want grammar-constrained sampling for reliable structured output (JSON, SQL, code) without prompt engineering.
- You have an existing GGUF library or want access to the broader quantization ecosystem.
- You need built-in metrics, health endpoints, and a more mature operational surface.
For Contra Collective's typical client work (LLM backends sitting behind a Next.js commerce frontend, agents querying NetSuite, RAG over product catalogs), llama-server wins almost every time. The continuous batching alone justifies the choice once you have more than one user.
A Practical Setup: llama-server on M5 Pro
# Install llama.cpp with Metal support
brew install llama.cpp
# Download a GGUF model (Llama 3.1 8B Instruct Q4_K_M)
huggingface-cli download \
bartowski/Meta-Llama-3.1-8B-Instruct-GGUF \
Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
--local-dir ./models
# Start server with continuous batching, 4 parallel slots, 8K context
llama-server \
-m ./models/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
--host 0.0.0.0 \
--port 8080 \
-c 8192 \
-np 4 \
--metrics \
--slots
The -np 4 flag enables 4 parallel slots (concurrent requests share the model). The -c 8192 sets the total KV cache budget (split across slots). --metrics enables Prometheus scraping at /metrics.
A Practical Setup: mlx_lm.server on M5 Pro
# Install MLX-LM
pip install mlx-lm
# Run the server pointing at an MLX-converted model
python -m mlx_lm.server \
--model mlx-community/Meta-Llama-3.1-8B-Instruct-4bit \
--host 0.0.0.0 \
--port 8080
That is the whole install. The model is pulled from Hugging Face on first run. There is no built-in concurrency control beyond the FastAPI default thread pool.
Decision Heuristic for Engineering Teams
If you are answering the question, "Which server should I put in front of a multi-user backend on Apple Silicon?", the answer is llama-server. The continuous batching, broader API surface, and operational maturity outweigh MLX's modest single-request throughput edge.
If you are answering the question, "Which server should I use for my single-user agent or local development?", mlx_lm.server is faster per request and dead simple to install. The difference at 8B between MLX and llama.cpp Metal is small but real.
The interesting middle ground is teams running RAG workloads with embeddings + chat + reranking on Apple Silicon. llama-server collapses that stack into one process, which simplifies deployment substantially. It is the right choice for that profile.
When This Applies to Your Stack
Backends with concurrency, structured output requirements, embeddings in the same process, or RAG retrieval over local data should default to llama-server on Apple Silicon. Single-user developer tools or MLX-native research workflows should default to mlx_lm.server. Most production AI infrastructure consulting engagements end up specifying llama-server with -np tuned to the expected concurrency profile, and a separate embeddings instance only when the chat model and embedding model are too large to colocate.
Choosing between these servers is the smallest decision in a local inference stack. Choosing the runtime, the model, the quantization, the hardware, and the orchestration layer (Kubernetes, Cloud Run, bare metal Mac mini cluster) are the bigger ones. We help teams make those calls without the trial-and-error.
FAQ
Does llama-server actually run on MLX? No. llama-server uses llama.cpp's Metal backend, not MLX. Some community work explores an MLX backend for llama.cpp, but it is not the default and not production-ready as of May 2026.
Can mlx_lm.server handle multiple concurrent requests? It can serve them through FastAPI's thread pool, but they serialize at the inference engine. There is no continuous batching in stable releases. Experimental batching support exists in mlx-lm trunk but is not production-grade.
Are the GGUF and MLX 4-bit quantizations equivalent quality? Roughly. MLX 4-bit group-wise quantization and GGUF Q4_K_M produce similar quality on standard benchmarks (within 1 to 2 percent on MMLU, GSM8K). Neither dominates across all model families.
Can I run both servers on the same Mac? Yes, with separate models and separate ports. Memory is the constraint. An M5 Pro 48 GB can hold an 8B GGUF and an 8B MLX model simultaneously without pressure. A 70B on either side will saturate.
Does llama-server support tool calling like the OpenAI API? Through grammar-constrained sampling (GBNF), it can enforce JSON output. Native OpenAI-style function calling is partial as of llama.cpp b4500+; check the latest release for full schema-aware function calling support.
More from the lab.
Perplexity Computer vs Claude Code: AI Developer Agents Compared for Engineering Teams in 2026
Perplexity Computer and Claude Code are both getting called AI agents for developers. That framing obscures more than it reveals. They are built on fundamentally different architectures, target different workflows, and fail in completely different ways.
Claude Sonnet 4.6 vs Gemini 3.1 Pro: SWE-Bench Verified Tested (2026)
Most of the 2026 model comparison content has been written about Opus 4.7 versus the rest of the frontier. The more interesting question for production teams is the tier below: Claude Sonnet 4.6 versus Gemini 3.1 Pro. Both ship as the mid-priced workhorse in their respective stacks. Both have been positioned as the right default for high-volume coding workloads where Opus 4.7 or Gemini 3.1 Ultra are overkill on cost.
mlx-lm Speculative Decoding on Apple Silicon: Benchmarks and Configuration (2026)
Speculative decoding has been the headline throughput optimization on CUDA hardware for two years. Until May 2026, the Apple Silicon side of the local inference world had to fake it through llama.cpp's experimental draft model support or skip it entirely. The release of mlx-lm 0.21 changed that. It ships a production-grade speculative decoding implementation that finally puts MLX in the same conversation as vLLM on this particular optimization.