L2 · 11 · Local Models: Ollama, LM Studio, MLX

Running models on your own machine gives you privacy, offline use, zero per-token cost and a much better feel for how models behave. This page covers choosing a model that fits your hardware, quantisation, the main local runners (including MLX on Apple silicon), and when local is the right call.

Level 245 minTracks 13, 18Last verified 2026-10

By the end of this page you can

When local makes sense

Good fit

  • Sensitive data that must not leave the machine (where policy allows local AI)
  • Offline work, travel
  • High-volume simple tasks: classification, extraction, embeddings
  • Learning and experimenting freely
  • Prototyping before choosing a hosted model

Poor fit

  • Hard reasoning, large refactors, long agentic tasks: frontier hosted models are much stronger
  • Very long contexts on modest hardware
  • Team-wide production serving (needs proper GPU infrastructure, Level 4)

Will it fit? A memory rule of thumb

memory needed ≈ parameters × (bits per weight / 8)  +  20–30% overhead (context / KV cache, runtime)
Model size16-bit8-bit4-bitRuns comfortably on
1–4 B2–8 GB1–4 GB~1–3 GBAlmost any modern laptop
7–9 B~16 GB~8–9 GB~5–6 GB16 GB laptop (Apple silicon or a decent GPU)
12–14 B~28 GB~15 GB~8–10 GB24–32 GB unified memory or a 12–16 GB GPU
30–35 B~70 GB~35 GB~20 GB36–64 GB Mac or a 24 GB GPU
70 B+140 GB+~75 GB~40 GB+64–128 GB Mac or multi-GPU workstation
Why Macs are popular for local AI Apple silicon uses unified memory shared by CPU and GPU, so a Mac with 32–128 GB can hold models that would need expensive GPUs on a PC. Memory bandwidth largely determines generation speed. Mixture-of-Experts models activate only part of their weights per token, so they can run faster than their total size suggests (but still need the memory to hold all weights).

Quantisation

Quantisation stores weights with fewer bits (for example 4 instead of 16). Models become 3–4× smaller and faster, with a modest quality loss that is usually acceptable at 4–8 bits.

FormatUsed byNotes
GGUF (e.g. Q4_K_M, Q5_K_M, Q8_0)llama.cpp, Ollama, LM StudioQ4_K_M is a common balance. Q5/Q6 is better quality, Q8 is near-original.
MLX (4-bit, 8-bit)MLX / mlx-lm on Apple siliconOptimised for Macs; many ready-made conversions are published by the community
GPU formats (AWQ, GPTQ, FP8…)GPU servers (vLLM etc.)Level 4

Rule: prefer a bigger model at 4-bit over a smaller model at 16-bit for the same memory, then test on your own tasks.

The local runners

Ollama: simplest CLI + server

ollama pull llama3.2              # small, fast general model
ollama run llama3.2 "Summarise our 14-day return policy in one line: ..."
ollama pull nomic-embed-text      # embeddings for page 05
ollama list                       # installed models
# OpenAI-compatible API at http://localhost:11434/v1

LM Studio: desktop app

Browse, download and chat with models in a GUI. It shows whether a model fits your memory. Start its local server to get an OpenAI-compatible endpoint (by default on http://localhost:1234/v1). Good for comparing models side by side.

llama.cpp: the engine

llama-server -m ./models/some-model-Q4_K_M.gguf --port 8080
# OpenAI-compatible API at http://localhost:8080/v1

MLX on Apple silicon

uv tool install mlx-lm            # or: pip install mlx-lm
mlx_lm.generate --model mlx-community/<model>-4bit --prompt "Explain opaque types in Scala 3"
mlx_lm.server   --model mlx-community/<model>-4bit --port 8081   # OpenAI-compatible server

MLX also supports fine-tuning (LoRA) on a Mac, which is covered in Level 3.

One client, any runner

Because all of these speak the OpenAI-compatible API, the clients from pages 03–06 work unchanged. Just set the base URL:

RunnerLLM_BASE_URL
Ollamahttp://localhost:11434/v1
LM Studiohttp://localhost:1234/v1
llama.cpp serverhttp://localhost:8080/v1 (or the port you chose)
mlx_lm.serverhttp://localhost:8081/v1 (the port you chose)

Benchmark on your own tasks

import json
import os
import time

import httpx

BASE = os.environ.get("LLM_BASE_URL", "http://localhost:11434/v1")
MODEL = os.environ.get("LLM_MODEL", "llama3.2")

TASKS = [  # (prompt, check) - checks are deliberately simple; use golden sets for real evals
    ("Classify as PICKUP, RETURN, PAYMENT or OTHER. Label only: 'My card was charged twice'", lambda a: "PAYMENT" in a.upper()),
    ("What is 17% of 2,400? Number only.", lambda a: "408" in a),
    ("Return only JSON {\"sku\":string,\"qty\":int} for: two chargers, sku CHARGER", lambda a: '"qty": 2' in a or '"qty":2' in a),
]


def run(prompt: str) -> tuple[str, float, float, int]:
    start, first, chunks, text = time.perf_counter(), None, 0, []
    with httpx.stream("POST", f"{BASE}/chat/completions", timeout=120,
                      json={"model": MODEL, "stream": True, "temperature": 0,
                            "messages": [{"role": "user", "content": prompt}]}) as r:
        r.raise_for_status()
        for line in r.iter_lines():
            if not line.startswith("data: ") or line.endswith("[DONE]"):
                continue
            delta = json.loads(line[6:])["choices"][0].get("delta", {}).get("content")
            if delta:
                first = first or time.perf_counter()
                chunks += 1
                text.append(delta)
    end = time.perf_counter()
    return "".join(text), (first or end) - start, end - start, chunks


if __name__ == "__main__":
    passed = 0
    for prompt, check in TASKS:
        answer, ttft, total, chunks = run(prompt)
        ok = check(answer)
        passed += ok
        rate = chunks / max(total - ttft, 1e-6)
        print(f"{'PASS' if ok else 'FAIL'}  first-token {ttft:.2f}s  ~{rate:.0f} chunks/s  {answer.strip()[:60]!r}")
    print(f"\n{MODEL}: {passed}/{len(TASKS)} passed")

Run it for 2–3 candidate models and keep the results in your learning log. You will quickly build intuition for which sizes handle which tasks.

Practical tips

Try it (30 min)
  1. Install Ollama or LM Studio. Pull a ~3B model and an ~8B model (4-bit).
  2. Run bench.py against both. Compare speed and pass rate.
  3. Point the Store Assistant (page 04, --live) at each. Which one calls tools reliably?
  4. On a Mac: try the same model via mlx_lm.server and compare speed with Ollama.

Key takeaways