Skip to content

Optimal Setup Guide

Tested hardware: MacBook Pro M1 Max (64 GB). This page ties together every recommendation from the guide into one benchmark-backed setup. If you have a different RAM tier, start with the Model Selection Guide instead.

TL;DR

Here is the complete optimal configuration for M1 Max 64 GB. Run the setup script at the bottom of this page to do it all at once.

Model Pull Command Speed RAM Budget Best For
gemma4:e4b-nvfp4 ollama pull gemma4:e4b-nvfp4 60 tok/s ~9.3 GB Daily driver — fast, small, pairs with anything
qwen3-coder:30b ollama pull qwen3-coder:30b 56 tok/s ~33 GB Coding — best tool calling, 10/10 quality
deepseek-r1:14b ollama pull deepseek-r1:14b 22 tok/s ~49 GB Reasoning — chain-of-thought, but watch the KV cache
gemma4:e2b-nvfp4 ollama pull gemma4:e2b-nvfp4 93 tok/s ~6.8 GB Maximum speed — fastest model tested
qwen3.6:35b-a3b-nvfp4 ollama pull qwen3.6:35b-a3b-nvfp4 54 tok/s ~22 GB MoE efficiency — 35B params, only 3B active

One-Time System Config

# Raise GPU memory limit (64 GB machines)
sudo sysctl iogpu.wired_limit_mb=57344

# Install swap-guard (auto-purges stale swap)
cd scripts/ && chmod +x install-swap-guard.sh && sudo ./install-swap-guard.sh

# Set KEEP_ALIVE=0s (unload models immediately)
export OLLAMA_KEEP_ALIVE=0s

System configuration

These are one-time changes. Do them once and forget about them.

Raise the GPU Memory Limit

macOS caps GPU-addressable memory at ~48 GB by default on 64 GB machines. Raise it to 57344 MB to give models access to the full pool:

sudo sysctl iogpu.wired_limit_mb=57344

Make it permanent by adding to /etc/sysctl.conf:

echo "iogpu.wired_limit_mb=57344" | sudo tee -a /etc/sysctl.conf

See the Memory Management Guide for safe limits by RAM tier and the warning about setting it too high.

Install swap-guard

Stale swap from previous model runs costs 28–31% tok/s on small models — even when you have 40 GB of free RAM. macOS does not reclaim swap after models unload. swap-guard is a launchd agent that monitors swap every 60 seconds and auto-purges when stale swap exceeds 1 GB.

cd scripts/
chmod +x install-swap-guard.sh
sudo ./install-swap-guard.sh

See the Swap Impact page for full install instructions, status checks, and uninstall.

Set KEEP_ALIVE=0s

By default, Ollama keeps models loaded for 5 minutes after the last request. That is 9+ GB of memory sitting idle. KEEP_ALIVE=0s unloads the model immediately after the response is complete:

export OLLAMA_KEEP_ALIVE=0s

Add it to your shell profile (~/.zshrc or ~/.bashrc) to make it permanent. See the Memory Management Guide for the full breakdown of when to use it and when not to.


Model selection

These are the five recommended models for M1 Max 64 GB, ordered by use case. All data is from my Model Comparison benchmarks.

Daily Driver: gemma4:e4b-nvfp4

60.4 tok/s, ~9.3 GB budget, 2.0s cold start

This is the most versatile model. It is fast enough for interactive chat, small enough to pair with any other model, and has excellent quality (8/10). The NVFP4 quantization keeps the file size at 8.8 GB while maintaining good fidelity.

ollama pull gemma4:e4b-nvfp4

Coding: qwen3-coder:30b

55.7 tok/s, ~33 GB budget, 7.8s cold start

The best coding model I have tested. 10/10 quality for code generation, best tool calling support of any model on Apple silicon. The cold start is slow (7.8s), but once loaded it generates at 56 tok/s.

ollama pull qwen3-coder:30b

Reasoning: deepseek-r1:14b

22.2 tok/s, ~49 GB budget, 2.79s cold start

10/10 quality for chain-of-thought reasoning, but there is a catch: the model itself is only 9 GB, but its KV cache at 128K context is 40 GB. That brings the total to ~49 GB — nearly filling the machine. If you reduce context to 32K, the KV cache drops to 10 GB and the total falls to ~19 GB.

ollama pull deepseek-r1:14b

See the Context Length Footgun page for the full story on how context length affects memory.

Maximum Speed: gemma4:e2b-nvfp4

93.1 tok/s, ~6.8 GB budget, 1.51s cold start

The fastest model I have tested. Tiny footprint, instant cold start. Great for high-throughput tasks, summarization, or when you just want a snappy chat experience. Quality is 9/10 — surprisingly good for its size.

ollama pull gemma4:e2b-nvfp4

MoE Efficiency: qwen3.6:35b-a3b-nvfp4

54.3 tok/s, ~22 GB budget, 5.0s cold start

35 billion total parameters, but only 3 billion active per token. This means it runs nearly as fast as a 3B model while delivering quality closer to a 35B model. The MoE architecture is the best quality-to-speed ratio on Apple silicon.

ollama pull qwen3.6:35b-a3b-nvfp4

Co-residence config

Running two models simultaneously lets you specialize: use a fast model for simple queries and a powerful model for complex reasoning.

The Best Pair

gemma4:e4b-nvfp4 + qwen3-coder:30b

Model Budget
gemma4:e4b-nvfp4 ~9.3 GB
qwen3-coder:30b ~33 GB
Total ~42 GB
Headroom 13 GB

That is 42 GB for models, leaving 13 GB for macOS and other apps. This is the ultimate combo: fast general chat + excellent coding.

What NOT to Pair

DeepSeek R1 14B is the pairing challenge. Its 40 GB KV cache at 128K context means it cannot share memory with any other large model:

Pair Total Why
deepseek-r1:14b + qwen3-coder:30b ~82 GB Exceeds 64 GB by 18 GB
deepseek-r1:14b + gemma4:26b-nvfp4 ~68 GB Exceeds 64 GB by 4 GB
deepseek-r1:14b + qwen3.6:35b-a3b-nvfp4 ~71 GB Exceeds 64 GB by 7 GB

See the Co-Residence Guide for the full pairing table and context-length tradeoffs.

KEEP_ALIVE for Burst-and-Unload

With KEEP_ALIVE=0s, each model is loaded on demand and unloaded immediately. This is the key to making multi-model setups work without manual management:

# Switch between models — each loads, runs, and unloads
ollama run gemma4:e4b-nvfp4 "Quick question" --keep-alive 0s
ollama run qwen3-coder:30b "Write a function" --keep-alive 0s

See the Burst & Unload pattern for the full trade-off analysis.


Serving engine choice

Ollama for Daily Use

Ollama is my default. It is the simplest setup, has the best model management (ollama pull / ollama run), native tool calling, and built-in OpenAI-compatible API. It also supports KEEP_ALIVE for burst-and-unload and multi-model setups.

Ollama 0.19+ uses MLX for safetensors models automatically — so you get MLX-level performance without the Python setup.

MLX (Rapid-MLX) for Max Throughput

For maximum token throughput on a single model, switch to MLX. Rapid-MLX generates at 51.4 tok/s on a 35B MoE model — 38% faster than mlx_lm. But it has a high prefill overhead (~28s for 2K tokens), so it is only worth it for long-form generation.

For interactive work, oMLX is the better MLX choice — its prefill is 5x faster than Rapid-MLX.

See the MLX Engine Comparison for the full head-to-head, and the Serving Comparison for when to use each engine.


Context length tuning

Avoid 128K context unless you actually need it. Context length is the biggest hidden memory cost.

Context Cost Table

Context KV Cache (9B) KV Cache (26B) KV Cache (30B coder) KV Cache (R1 14B)
4,096 0.33 GB ~0.06 GB 0.5 GB 1.25 GB
8,192 0.66 GB ~0.12 GB 1.0 GB 2.5 GB
16,384 1.31 GB ~0.24 GB 2.0 GB 5 GB
32,768 2.62 GB ~0.48 GB 3.8 GB 10 GB
131,072 10.49 GB ~1.92 GB 15.2 GB 40 GB
Model Recommended Context Why
gemma4:e4b-nvfp4 8,192 Fast chat does not need long context
gemma4:e2b-nvfp4 4,096 Speed-focused, short queries
qwen3-coder:30b 32,768 Code needs moderate context
deepseek-r1:14b 8,192–32,768 128K costs 40 GB in KV cache alone
qwen3.6:35b-a3b-nvfp4 16,384 Good balance for MoE reasoning

See the Context Length Footgun page for the full analysis, including how to set per-model context length in your config.


Monitoring

Keep an eye on your system while running models. Here are the three most-used commands:

macmon — Thermal + Memory Overview

# One-shot overview (no sudo needed)
macmon --once

Shows CPU/GPU power, thermal pressure, memory pressure, and fan speed. Install with brew install macmon. See the Memory Management Guide for details.

Swap Check

sysctl vm.swapusage

Look for total = X.XX GB. If it is above 1 GB and no large model is loaded, you have stale swap. See the Swap Impact page for the full story.

System Memory Pressure

memory_pressure

If you see Pressure: 30%+ or State: WK (wakeup), the system is actively managing memory pressure — swap is likely involved.


The complete setup script

Run this once on a fresh machine. It does everything: installs Ollama, configures the system, pulls the recommended models, and verifies everything works.

#!/bin/bash
# Apple Silicon LLM Guide — Optimal Setup Script
# M1 Max 64 GB target. Adjust GPU limit for other RAM sizes.

set -e

echo "=== Apple Silicon LLM — Optimal Setup ==="

# 1. Install Ollama (if not installed)
if ! command -v ollama &> /dev/null; then
    echo "Installing Ollama..."
    curl -fsSL https://ollama.com/install.sh | sh
else
    echo "Ollama already installed ($(ollama --version))"
fi

# 2. Raise GPU memory limit (64 GB machines)
echo "Setting GPU memory limit to 57344 MB..."
sudo sysctl iogpu.wired_limit_mb=57344
if ! grep -q "iogpu.wired_limit_mb=57344" /etc/sysctl.conf 2>/dev/null; then
    echo "iogpu.wired_limit_mb=57344" | sudo tee -a /etc/sysctl.conf
    echo "Made permanent in /etc/sysctl.conf"
fi

# 3. Install swap-guard
if [ -f scripts/install-swap-guard.sh ]; then
    echo "Installing swap-guard..."
    chmod +x scripts/install-swap-guard.sh
    sudo ./scripts/install-swap-guard.sh
else
    echo "WARNING: scripts/install-swap-guard.sh not found. Skipping."
    echo "Clone the repo or download from:"
    echo "  https://github.com/j3n0v4/apple-silicon-llm-guide"
fi

# 4. Set KEEP_ALIVE=0s
echo "Setting OLLAMA_KEEP_ALIVE=0s..."
if ! grep -q "OLLAMA_KEEP_ALIVE=0s" ~/.zshrc 2>/dev/null; then
    echo 'export OLLAMA_KEEP_ALIVE=0s' >> ~/.zshrc
    echo "Added to ~/.zshrc"
fi
if ! grep -q "OLLAMA_KEEP_ALIVE=0s" ~/.bashrc 2>/dev/null; then
    echo 'export OLLAMA_KEEP_ALIVE=0s' >> ~/.bashrc
    echo "Added to ~/.bashrc"
fi
export OLLAMA_KEEP_ALIVE=0s

# 5. Pull recommended models
echo ""
echo "=== Pulling recommended models ==="
echo "(This will take a while depending on your internet speed)"

MODELS=(
    "gemma4:e4b-nvfp4"
    "qwen3-coder:30b"
    "deepseek-r1:14b"
    "gemma4:e2b-nvfp4"
    "qwen3.6:35b-a3b-nvfp4"
)

for model in "${MODELS[@]}"; do
    echo ""
    echo "--- Pulling $model ---"
    ollama pull "$model"
done

# 6. Verify everything works
echo ""
echo "=== Verification ==="

echo ""
echo "GPU memory limit:"
sysctl iogpu.wired_limit_mb

echo ""
echo "Swap status:"
sysctl vm.swapusage

echo ""
echo "swap-guard status:"
launchctl list | grep swap-guard || echo "swap-guard not running (check install)"

echo ""
echo "Pulled models:"
ollama list

echo ""
echo "Quick smoke test (gemma4:e4b-nvfp4):"
ollama run gemma4:e4b-nvfp4 --keep-alive 0s "Say 'Setup complete!' in exactly 3 words"

echo ""
echo "=== Setup complete! ==="
echo "Restart your terminal or run 'source ~/.zshrc' to apply KEEP_ALIVE=0s."
echo "See https://j3n0v4.github.io/apple-silicon-llm-guide/guides/optimal-setup/ for docs."

What's next