> ## Content Index
> Fetch the complete content index at: https://adrianoamalfi.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# LocalAI su macOS: accelerazione GPU con Metal, passo passo
- URL: https://adrianoamalfi.com/tech-scratchpad/localai-gpu-metal-macos/
- Published: 2026-09-13T08:53:31.000Z
- Updated: 2026-09-28T12:12:09.000Z
- Description: LocalAI v4.9.0 su macOS Apple Silicon con accelerazione GPU via Metal: installazione, configurazione del modello e verifica del caricamento su GPU.
- Author: Adriano Amalfi
- Tags: AI Agentica, Scratchpad, AI Tools, LLM, Tutorial, #Tech Scratchpad: Appunti avanzati e esperimenti per esperti e amatoriali

**Testato il:** 2026-09-13 **Ambiente:** macOS 26.6.2 (Tahoe) · Apple M2 (arm64) · LocalAI v4.9.0 (Homebrew) · Go 1.27.1 (solo per build da sorgente)

## Prerequisiti

- Un Mac Apple Silicon (M1, M2, M3, M4, M5): GPU e CPU [condividono la stessa memoria unificata](https://adrianoamalfi.com/inferenza-on-device-a20-pro-memoria/), ed è lì che Metal lavora meglio
- Homebrew installato e aggiornato
- Almeno [8 GB di RAM unificata](https://adrianoamalfi.com/local-ai-non-e-piu-hobby/) e una decina di GB di spazio libero: il primo avvio scarica il backend llama.cpp e il modello (\~900 MB)
- Xcode Command Line Tools (`xcode-select --install`), obbligatori solo se vuoi compilare da sorgente

Docker su macOS resta su CPU — Docker Desktop gira in una VM Linux che non ha accesso alla GPU Metal: i container LocalAI restano su CPU e la guida non funziona. Non usare Docker per questa guida: [serve il binario nativo](https://adrianoamalfi.com/docker-wsl-ai-lab/).

## 1\. Verifica hardware e sistema

Apri Terminale e controlla architettura, versione di macOS e GPU:

```bash
uname -m
sw_vers
system_profiler SPDisplaysDataType | grep -E "Chipset|Metal"
```

Risultato atteso: `arm64` come architettura, una versione di macOS tipo `26.6.2`, e nel report della GPU le righe `Chipset Model: Apple M2` e `Metal Support: Metal 3`. Se `uname -m` risponde `x86_64`, stai girando sotto Rosetta: apri Terminale in modalità nativa (arm64) prima di continuare. Se hai un Mac Intel, la guida arriva in fondo con le limitazioni del caso.

## 2\. Installa LocalAI con Homebrew

```bash
brew install localai
local-ai --version
```

Risultato atteso: Homebrew installa il binario `local-ai` e `local-ai --version` risponde `v4.9.0` (la release stabile di agosto 2026). La formula esiste per macOS arm64 e per Intel. In alternativa puoi scaricare il DMG da GitHub releases (un launcher nella menu bar che gestisce il server) o il binario diretto, ma Homebrew resta la strada più semplice da aggiornare.

Se vuoi la build da sorgente (per esempio per testare una modifica al backend), il percorso è `git clone` del [repository di LocalAI](https://github.com/go-skynet/LocalAI?ref=adrianoamalfi.com) seguito da `make build`: richiede Go (la formula Homebrew lo compila con Go 1.27.1) e Xcode con il toolchain Metal, e produce il binario `local-ai` nella directory del repo.

## 3\. Avvia il server e installa un modello

Crea una directory di lavoro e avvia LocalAI con un modello piccolo del catalogo. Il comando `run` installa il modello e fa partire il server in primo piano:

```bash
mkdir -p ~/localai-mps && cd ~/localai-mps
DEBUG=true local-ai run llama-3.2-1b-instruct:q4_k_m
```

Risultato atteso, nell'ordine: il backend `llama-cpp` viene scaricato (su Apple Silicon la build con supporto Metal), poi il file GGUF (\~800 MB per llama-3.2-1b in Q4\_K\_M), poi i log con `API ready on http://localhost:8080` e `model ready`. La variabile `DEBUG=true` rende i log molto più verbosi: è quello che ci serve al passo 4, e in produzione puoi toglierla.

Con il server acceso, in un secondo terminale puoi chattare subito:

```bash
local-ai chat --model llama-3.2-1b-instruct:q4_k_m
```

Risultato atteso: un prompt interattivo che risponde in pochi secondi. La web UI è su [localhost:8080](http://localhost:8080/?ref=adrianoamalfi.com): lì trovi la chat, i modelli installati e lo stato del sistema.

## 4\. Forza il caricamento su GPU

Il gotcha: senza \`gpu\_layers\` resti in CPU — Le entry del catalogo LocalAI impostano \`f16: true\` ma \*\*non\*\* \`gpu\_layers\`: senza quel valore llama.cpp carica il modello in CPU anche se il backend Metal c'è. È il motivo per cui molti credono che "Metal non funziona". Verifica la configurazione generata:

```bash
ls -la ~/localai-mps/models/
```

Risultato atteso: nella directory trovi il file `.gguf` e un YAML di configurazione (es. `llama-3.2-1b-instruct-q4_k_m.yaml`). Aprilo e aggiungi `gpu_layers` a livello top, vicino a `f16: true`. Il YAML generato ha anche campi di template e opzioni: lasciali intatti. Estratto minimo dei campi che contano:

```yaml
name: llama-3.2-1b-instruct
backend: llama-cpp
f16: true
gpu_layers: 999
context_size: 8192
```

`999` non è un errore di battitura: significa "scarica su GPU tutti i layer che il modello ha" (llama-3.2-1b ne ha 16, quindi `999` equivale a 16). I valori più bassi servono solo quando il modello non entra in memoria. Riavvia il server (Ctrl+C e rilancia il comando del passo 3) e guarda i log del caricamento.

Risultato atteso nei log con `DEBUG=true`:

```console
ggml_metal_device_init: GPU name:   MTL0
ggml_metal_device_init: has unified memory    = true
llm_load_tensors: offloading 16 repeating layers to GPU
llm_load_tensors: offloaded 16/16 layers to GPU
```

Il segnale stabile da cercare — Il formato esatto delle righe \`ggml\_metal\*\` cambia tra le versioni di llama.cpp impacchettate da LocalAI, il verdetto \`offloaded N/N layers to GPU\` invece è stabile: cerca quello. Se vedi \`offloaded 0/16\`, il modello è rimasto in CPU e \`gpu\_layers\` non è stato letto.

## 5\. Chiama l'API

LocalAI espone un'API compatibile con OpenAI. Verifica i modelli caricati:

```bash
curl http://localhost:8080/v1/models
```

Risultato atteso: un JSON con l'id del modello, `llama-3.2-1b-instruct:q4_k_m` (usa quell'id esatto nelle chiamate, è quello che il server espone). Poi una chat completion:

```bash
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "llama-3.2-1b-instruct:q4_k_m", "messages": [{"role": "user", "content": "Racconta una barzelletta in tre righe"}], "max_tokens": 256}'
```

Risultato atteso: un JSON OpenAI-compatible con la risposta in `choices[0].message.content`. Se risponde, il server è pronto per qualsiasi client OpenAI (LangChain, Open WebUI, script custom): basta cambiare base URL e chiave API.

## 6\. Confronta CPU e GPU

Ora misura quanto vale Metal. Metti `gpu_layers: 0` nel YAML, riavvia e cronometra una generazione:

```bash
time curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "llama-3.2-1b-instruct:q4_k_m", "messages": [{"role": "user", "content": "Scrivi una poesia di quattro versi su Roma"}], "max_tokens": 128}'
```

Poi rimetti `gpu_layers: 999`, riavvia e rilancia lo stesso identico comando. Risultato atteso: il tempo totale della richiesta GPU è inferiore, ma con un modello da 1B il divario sui token generati è contenuto: il modello è piccolo e anche la CPU lo sforna veloce. Il guadagno grosso del Metal si vede nel prompt processing e con [modelli da 3B in su](https://adrianoamalfi.com/llm-locale-ubuntu-nvidia-16gb/). Se vuoi una prova più netta, installa `qwen3-4b` (`local-ai run qwen3-4b`) e ripeti il confronto: lì il divario CPU/GPU è difficile da non vedere.

## 7\. MPS o Metal: la distinzione che conta

Se sei arrivato qui cercando un flag "MPS" da attivare, chiudiamo con la precisazione onesta: **in LocalAI non esiste un backend chiamato MPS**. Su macOS l'accelerazione passa dal backend Metal di llama.cpp (ggml-metal), che è il backend GPU consigliato per Apple Silicon e viene scelto in automatico dalla rilevazione hardware di LocalAI.

La confusione nasce da tre cose che si chiamano quasi uguale e non sono la stessa cosa:

- **Metal Performance Shaders (MPS)** è la libreria di compute proprietaria di Apple. Il backend Metal di llama.cpp non la usa per i calcoli principali: usa shader Metal scritti dal progetto (i kernel `mul_mm` e `mul_mat_vec` in Metal Shading Language) e istruzioni simdgroup. Non c'è un flag "MPS" da attivare da nessuna parte.
- **`torch.backends.mps`** è il device MPS di PyTorch. Se arrivi da PyTorch, "MPS" lì indica la GPU Apple: stesso hardware, concetto diverso, nessuna corrispondenza con LocalAI.
- **llama.cpp ha avuto un backend MPS** (compilato con `GGML_MPS`, basato su MPSMatrixMultiplication): è stato rimosso anni fa a favore del backend Metal unificato. Per questo nei log cerchi `ggml_metal`, non "mps".

Non serve nemmeno toccare `LOCALAI_FORCE_META_BACKEND_CAPABILITY`: quella variabile serve a forzare i backend CUDA, ROCm o SYCL dove la rilevazione automatica sbaglia, e su macOS non ha un valore "metal". La rilevazione automatica sceglie la build Metal del backend da sola.

E se vuoi spremere la M-series, c'è anche il backend **MLX** di LocalAI (supportato da agosto 2025): MLX è il framework array di Apple, costruito sopra Metal, e per alcuni modelli su Apple Silicon è più veloce di llama.cpp. È il secondo esperimento da fare, non il primo: MLX copre meno formati e la compatibilità GGUF è più stretta.

## Verifica

Hai la GPU attiva se e solo se:

1. `local-ai --version` risponde `v4.9.0` (o la release corrente)
2. I log del caricamento mostrano `ggml_metal*` e `offloaded 16/16 layers to GPU` (N/N, non 0/N)
3. `curl http://localhost:8080/v1/models` risponde con l'id del modello
4. `/v1/chat/completions` risponde in pochi secondi
5. Il confronto del passo 6 con `gpu_layers: 0` è più lento di quello con `gpu_layers: 999`

Se il punto 2 fallisce ma gli altri passano, il server funziona: sei solo in CPU. Correggi `gpu_layers` nel YAML e riavvia.

## Limiti noti

- **Il guadagno dipende dal chip e dalla RAM unificata.** Su un MacBook Air M1 base, un modello 1B va veloce anche in CPU: il test onesto è su modelli 3-8B, dove Metal fa la differenza vera.
- **Non tutte le quantizzazioni hanno kernel Metal.** Formati sperimentali come TQ2\_0 abortiscono con errori tipo "not implemented in ggml-metal-device.cpp". Resta su Q4\_K\_M, Q5\_K\_M o Q8\_0.
- **La memoria è condivisa.** Un modello grande compete con browser, IDE e resto del sistema: se il Mac inizia a swappare, Metal non aiuta.
- **Docker su macOS non passa la GPU.** I container LocalAI girano su CPU: usa il binario nativo, come in questa guida.
- **Intel Mac.** macOS Tahoe è l'ultima versione che li supporta, i binari ufficiali LocalAI sono arm64 e la GPU AMD dei Mac Intel dà risultati modesti. Su Intel la strada è compilare da sorgente e le aspettative vanno ridimensionate.
- **Le entry del catalogo non sempre impostano `gpu_layers`.** Ogni volta che aggiungi un modello, verifica il YAML generato: il caricamento su GPU non è garantito dal solo fatto che il backend Metal esista.
- **Versioni.** La guida documenta LocalAI v4.9.0 (settembre 2026). Le righe di log di llama.cpp cambiano tra versioni: il segnale stabile da cercare è `offloaded N/N layers to GPU`.