AI DevelopmentLocal LLMs

MLX + mlx-lm: Run and Fine-Tune LLMs on Your Mac

September 22, 2026
12 min read
Apple Silicon chip running a local LLM terminal
Share:

Running a capable LLM on your own Mac used to mean fighting VRAM limits, CUDA-only tooling, or paying for cloud GPUs by the hour. Apple’s MLX stack changes that equation: it treats the Mac’s unified memory as one pool, speaks Metal natively, and with the mlx-lm package you can generate, quantize, and fine-tune models with a handful of commands.

In this deep-dive I show you the full loop: install mlx-lm, chat with the default Llama-3.2-3B, convert and 4-bit quantize any Hugging Face model in seconds, fine-tune it with LoRA on top of the quantized weights, and publish to mlx-community. Plus real M5 vs M4 numbers from Apple’s September 2026 research post.

1. The Problem: Great Mac, No Good Way to Run LLMs

Most open LLM tooling was designed around NVIDIA GPUs: discrete VRAM, CUDA kernels, and servers that never sleep. A MacBook with 18GB or 36GB of unified memory looks weak on paper next to an 80GB H100, yet in practice the Mac can hold a 14B model in memory while the cloud bill keeps ticking. The missing piece was software that actually uses the Apple Silicon architecture instead of emulating the CUDA world.

The Most Common Mistake

Installing a generic x86 inference stack on a Mac and concluding “Apple Silicon is too slow for LLMs”. Without unified-memory-aware execution and Metal kernels, you leave most of the machine’s bandwidth unused. MLX exists precisely to close that gap.

That is what MLX solves: an open-source array framework (NumPy-like API) purpose-built for Apple Silicon, with first-class support for training and inference. And mlx-lm is its LLM layer: thousands of Hugging Face models with one command, quantization, LoRA and QLoRA fine-tuning, an OpenAI-compatible server, and multi-Mac distributed inference.

2. The Minimum Concepts You Actually Need

đź§ 

Unified memory

CPU and GPU share the same memory pool, so MLX runs ops on either processor without copying weights around. More unified memory means bigger models: roughly 8B on 18GB, 30B+ class models on 64GB+, and 70B on high-memory Macs.

⚡

Metal + M5 Neural Accelerators

MLX targets Metal 4 TensorOps and each M5 GPU core carries a dedicated Neural Accelerator for matrix multiplication. Prefill (time to first token) is compute-bound and flies; decode stays memory-bandwidth-bound.

đź—ś

4-bit quantization

Shrinking weights to 4 bits cuts memory roughly 4x with small quality loss. mlx_lm.convert quantizes a 7B model in seconds on a Mac, and every mlx-community *-4bit repo is ready to download and run.

đź§Ş

LoRA / QLoRA

Low-Rank Adaptation trains tiny adapter matrices instead of all weights. Point mlx_lm.lora at a quantized model and you get QLoRA: fine-tuning a 7B model on a 32GB Mac at ~250 tokens/sec.

Versions used in this guide

mlx-lm 0.31.3 (April 2026, latest stable on PyPI), Python 3.8+, macOS 15+ recommended for memory wiring on large models. Default model for generate and chat: mlx-community/Llama-3.2-3B-Instruct-4bit.

3. Tutorial: From Zero to Fine-Tuned Model

Everything below runs on any Apple Silicon Mac. No cloud, no Docker, no CUDA. I use the exact commands from the mlx-lm README and LoRA docs, so run mlx_lm.<command> --help whenever you want the full option list.

Step 1 — Install

pip install mlx-lm
pip install "mlx-lm[train]"  # needed for LoRA fine-tuning

Step 2 — Generate and chat

The default model downloads automatically on first run. Chat keeps context for the whole REPL session.

mlx_lm.generate --model mlx-community/Llama-3.2-3B-Instruct-4bit --prompt "Explain unified memory in one paragraph"
mlx_lm.chat --model mlx-community/Llama-3.2-3B-Instruct-4bit

Step 3 — Python API

from mlx_lm import load, generate
model, tokenizer = load("mlx-community/Mistral-7B-Instruct-v0.3-4bit")
messages = [{"role": "user", "content": "Write a haiku about Metal"}]
prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True)
text = generate(model, tokenizer, prompt=prompt, verbose=True)

Step 4 — Convert and 4-bit quantize any HF model

The -q flag quantizes; --upload-repo publishes straight to the mlx-community org. Conversion of a 7B model takes seconds on a Mac.

mlx_lm.convert --model mistralai/Mistral-7B-Instruct-v0.3 -q
mlx_lm.convert --model mistralai/Mistral-7B-Instruct-v0.3 -q --upload-repo mlx-community/my-4bit-mistral

Step 5 — Fine-tune with LoRA (or QLoRA)

If --model points to a quantized model you get QLoRA automatically. Data is a folder with train.jsonl plus optional valid.jsonl, or a Hugging Face dataset id. For the full flag list run mlx_lm.lora --help; for repeatable runs use a YAML config with mlx_lm.lora --config config.yaml.

mlx_lm.lora --model mistralai/Mistral-7B-v0.1 --train --data ./my-data --iters 600
mlx_lm.lora --model mlx-community/Mistral-7B-v0.3-4bit --train --data ./my-data --iters 1000 --batch-size 1 --num-layers 4

Step 6 — Evaluate, generate, fuse, upload

Test perplexity with --test, generate with the adapter attached, then fuse adapters into the weights and optionally upload or export to GGUF.

mlx_lm.lora --model <path_to_model> --adapter-path ./adapters --data ./my-data --test
mlx_lm.generate --model <path_to_model> --adapter-path ./adapters --prompt "Translate to Spanish: good morning"
mlx_lm.fuse --model mistralai/Mistral-7B-v0.1 --upload-repo mlx-community/my-lora-mistral-7b --hf-path mistralai/Mistral-7B-v0.1

Step 7 — Serve it (OpenAI-compatible)

mlx_lm.server --model mlx-community/Mistral-7B-Instruct-v0.3-4bit
curl localhost:8080/v1/models -H "Content-Type: application/json"

Golden rule

Always run mlx_lm.<command> --help before scripting anything. mlx-lm evolves fast (0.31.3 in April 2026), and the CLI help is the only flag list that cannot be outdated.

4. Benchmarks: M5 vs M4 With MLX (Apple, Sept 2026)

Apple’s Machine Learning Research team published LLM inference numbers on M5 vs M4 MacBook Pro using MLX, with the macOS release that enables M5 Neural Accelerators. The pattern is clean: prompt processing (prefill) is compute-bound and benefits up to ~4x, while token generation (decode) is memory-bandwidth-bound and improves 19–27%.

TTFT dense 14B

<10s on M5

Qwen3-14B 4-bit went from ~36s on M4 to ~8s on M5 in Apple’s test

TTFT 30B MoE

<3s on M5

Mixture-of-experts activates few params per token, ideal for unified memory

Decode (gen) speedup

+19–27% M5 vs M4

Explained by bandwidth: 153 GB/s (M5) vs 120 GB/s (M4), ~28% higher

FLUX-dev 1024px image

3.8x faster on M5

12B diffusion model, same Neural Accelerator story as LLM prefill

Honest read: decode gains are modest because no tensor unit raises the memory ceiling, and independent tests show runtimes like llama.cpp can beat MLX on raw prefill for some models. But for the agentic loop (huge prompts, tool results, whole codebases) the M5’s ~4x prompt-processing jump is the number that matters.

5. Common Errors (and Fixes)

Every one of these bites someone the first week. They are all cheap to fix once you know them.

đź”§

Model gets killed on load (large model, small Mac)

Check the wired-memory limit with sysctl iogpu.wired_limit_mb and raise it if your Mac has headroom; on macOS 15+ MLX can wire large-model memory. Otherwise drop to a smaller quant (4-bit) or fewer active params (MoE).

đź”§

Out of memory during LoRA

In order: use a quantized base model (QLoRA), --batch-size 1 plus --grad-accumulation-steps, --num-layers 4 or 8 instead of 16, --grad-checkpoint, shorter sequences. The 32GB recipe in the docs trains Mistral 7B fine.

đź”§

Adapters do nothing at generation time

You forgot --adapter-path on mlx_lm.generate, or you evaluated a fused model against unfused weights. Generate with the adapter flag; fuse only when you want a standalone model.

đź”§

Chat answers ignore your instruction format

You skipped the chat template. Always build the prompt with tokenizer.apply_chat_template(messages, add_generation_prompt=True) instead of raw string concatenation.

đź”§

No Neural Accelerator speedup on M5

You need the macOS release with M5 Neural Accelerator support (MLX targets Metal 4 TensorOps there). Update macOS, update mlx-lm, and re-run; no code changes needed, MLX picks the best kernel per machine.

đź”§

Stale flags copied from a blog post (possibly this one)

Run mlx_lm.generate --help, mlx_lm.convert --help, mlx_lm.lora --help, mlx_lm.fuse --help. Repos move fast; the CLI is ground truth.

Conclusion

The loop is the whole story: mlx_lm.generate to try, mlx_lm.convert -q to own the weights, mlx_lm.lora to specialize, mlx_lm.fuse to ship, mlx_lm.server to serve. All on a laptop, all on Apple Silicon’s unified memory, and on M5 with Neural Accelerators doing the heavy matrix math.

If you only do one thing this week: pip install mlx-lm and chat with the default 3B model. The moment a local model answers from your own machine, the cloud stops feeling mandatory.

Cheat Sheet

Run

  • • mlx_lm.generate --model ... --prompt ...
  • • mlx_lm.chat --model ...
  • • mlx_lm.server --model ...

Own

  • • mlx_lm.convert --model ... -q
  • • --upload-repo mlx-community/...
  • • load() + generate() in Python

Specialize

  • • mlx_lm.lora --train --data ...
  • • --test, --adapter-path, --config
  • • mlx_lm.fuse [--export-gguf]

Sources

Diego Rodriguez

Diego Rodriguez

Senior Full-Stack & AI Engineer

Diego has 10+ years of experience building production-grade AI-powered applications, from LLM orchestration and RAG pipelines to ML-driven risk detection and algorithmic trading systems.

Learn more about Diego →