All Posts
AIMay 24, 2026

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

Dimensionmlx_lm.server (MLX-LM)llama-server (llama.cpp)
BackendMLX (Metal, native Apple Silicon)llama.cpp (Metal backend, also CPU/CUDA)
API surfaceOpenAI Chat Completions, CompletionsOpenAI Chat, Completions, embeddings, reranking
Model formatMLX (safetensors), HF AutoConvertGGUF only
Quantization2, 3, 4, 6, 8 bit (MLX group-wise)Q2_K through Q8_0, K-quants, IQ-quants
Concurrent requestsSequential by default; experimental batchingContinuous batching, parallel slots
StreamingSSE, token-by-tokenSSE, token-by-token, with logprobs
Function callingManual via prompt templatesGrammar-constrained sampling (GBNF)
Memory residencyLoaded once, persistentLoaded once, with mmap for fast cold starts
Stable releasemlx-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)

Hardwaremlx_lm.server (4-bit)llama-server (Q4_K_M)
M4 Pro (24 GB)62 tok/s58 tok/s
M5 Pro (48 GB)81 tok/s74 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

Hardwaremlx_lm.serverllama-server
M4 Pro (24 GB)71 tok/s aggregate162 tok/s aggregate
M5 Pro (48 GB)94 tok/s aggregate218 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

ServerSingle requestConcurrency 2
mlx_lm.server8.4 tok/s9.1 tok/s aggregate
llama-server7.9 tok/s14.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

ServerSingle requestConcurrency 4
mlx_lm.server28 tok/s31 tok/s aggregate
llama-server26 tok/s79 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/embeddings for embedding models loaded alongside the chat model
  • /v1/rerank for reranking with cross-encoder GGUF models
  • /health and /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-lm and python -m mlx_lm.server is 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.

[ 02 ] — Keep Reading

More from the lab.

Jun 11, 2026AI

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.

Jun 11, 2026AI

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.

Ready when you are

Want to discuss this topic?

Start a Conversation